摘要

小常识

1.$$可变变量

$$a​ 是 PHP 中的一种特殊变量引用语法,被称为”可变变量”(variable variables)。它允许您使用一个变量的值作为另一个变量的名称。

让我们详细解释 $$a​ 的含义:

  1. $a​ 是一个普通的变量,其中存储了一个字符串值,通常用于表示变量的名称。
  2. $$a​ 使用 $a​ 的值作为变量名称,并尝试访问这个变量。

例如,如果 $a​ 的值为 “myVar”,那么 $$a​ 将尝试访问名为 $myVar​ 的变量。

下面是一个示例:

1
2
3
4
$a = "myVar";
$myVar = "Hello, world!";

echo $$a; // 输出 "Hello, world!"

在这个示例中,$a​ 包含字符串 “myVar”,然后 $$a​ 将根据 $a​ 的值尝试访问 $myVar​,并将 “Hello, world!” 打印出来。

需要注意的是,使用可变变量时要格外小心,因为它们可能会导致代码的可读性变差,并且在不慎使用时可能引发错误或安全问题。通常情况下,最好使用数组来管理变量,而不是可变变量,以提高代码的可维护性和安全性。

  1. 满足条件之后以数组形式打印出变量$$a。
  2. $$a可以理解为$($a),就是让$a的值成为这个大变量的变量名。
  3. 由此我们可以想到$GLOBALS — 引用全局作用域中可用的全部变量。
  4. 利用它,我们可以输出所有变量,所以构造payload:?c=GLOBALS

这时候$$a就变成了$GLOBALS。

2.X-Forwarded-For

X-Forwarded-For​ 是一个HTTP请求头,通常用于表示HTTP请求经过的代理服务器链中的客户端IP地址。这个头字段的值是一个逗号分隔的IP地址列表,最左边的IP地址表示最初的客户端IP,而最右边的IP地址表示最后一个代理服务器的IP。这是一个示例:

1
X-Forwarded-For: client, proxy1, proxy2

在这个示例中,client​ 是最初的客户端IP地址,proxy1​ 和 proxy2​ 是两个代理服务器的IP地址,它们按顺序处理了HTTP请求。

X-Forwarded-For​ 头的作用是帮助后端服务器了解请求的真实客户端IP地址,尤其在请求通过代理服务器、负载均衡器或反向代理服务器时很有用。默认情况下,HTTP请求的源IP地址是代理服务器的IP地址,而不是最初客户端的IP地址。通过查看X-Forwarded-For​ 头,后端服务器可以获取到客户端的真实IP地址。

然而,需要注意以下几点:

  1. 安全性问题: X-Forwarded-For​ 头是可伪造的。客户端可以在请求中添加自己构造的X-Forwarded-For​ 头,因此不能完全信任它来确定客户端的真实IP地址。因此,不应将其用于安全性相关的操作,如访问控制或身份验证。
  2. 代理链: 如果请求经过多个代理服务器,X-Forwarded-For​ 头中的IP地址列表可能会很长。您可能需要解析这个头来提取最初客户端的IP地址。
  3. 代理配置: 代理服务器必须正确配置以传递 X-Forwarded-For​ 头。如果代理服务器没有传递这个头或错误地修改了它,后端服务器将无法获取真实的客户端IP地址。

总之,X-Forwarded-For​ 头是有用的,但必须小心使用,特别是在考虑安全性时。在合适的情况下,它可以帮助您了解请求的来源。

3.POST请求(BP)

Content-Type: application/x-www-form-urlencoded

Content-Type: application/x-www-form-urlencoded​ 是一个HTTP头,通常用于指示HTTP请求的主体(body)数据格式。这个特定的Content-Type​值表示请求主体中包含的数据采用了经典的URL编码格式,通常用于HTML表单提交数据。

application/x-www-form-urlencoded​ 格式中,数据以键值对的形式发送,键值对之间使用 &​ 符号分隔,而键和值之间使用 =​ 符号分隔。例如,一个简单的表单提交可能会创建以下形式的数据:

1
name=John+Doe&age=30&city=New+York

4.foreach配合$$是典型变量覆盖漏洞

使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。

5.POST传参写shell

1.为什么利用base64编码一句话木马,因为如果直接写入,<?php exit();会截断。

2.利用a补位,如果不用a补位,一句话木马就解析不出来。

6.extract()函数变量覆盖漏洞

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 <?php
//flag in $flag
highlight_file(__FILE__);
include("flag.php");
$c=$_POST['sys'];
$key1 = 0;
$key2 = 0;
if(isset($_GET['flag1']) || isset($_GET['flag2']) || isset($_POST['flag1']) || isset($_POST['flag2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($flag1 == '8gen1' && $flag2 == '8gen1') {
if(isset($_POST['504_SYS.COM'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\?/", $c)){
eval("$c");

}
}
}
?>

@parse_str($_SERVER[‘QUERY_STRING’]);
extract($_POST);

$_SERVER​ 是 PHP 的一个超全局数组,用于存储有关服务器和当前脚本的信息。它包含了许多有用的键值对,可以用于访问和管理服务器环境的各种信息。以下是一些 $_SERVER​ 超全局数组中常见的键和其含义:

  1. $_SERVER['HTTP_USER_AGENT']​:包含用户代理字符串,用于识别用户的浏览器和操作系统。
  2. $_SERVER['HTTP_REFERER']​:包含引导用户到当前页面的 URL。
  3. $_SERVER['SERVER_NAME']​:包含当前服务器的主机名。
  4. $_SERVER['SERVER_ADDR']​:包含服务器的 IP 地址。
  5. $_SERVER['REQUEST_METHOD']​:包含当前请求的 HTTP 方法,如 GET、POST、或其他。
  6. $_SERVER['QUERY_STRING']​:包含查询字符串,即 URL 中问号后的部分。
  7. $_SERVER['REMOTE_ADDR']​:包含客户端的 IP 地址。
  8. $_SERVER['SCRIPT_FILENAME']​:包含当前执行的脚本的文件路径。
  9. $_SERVER['REQUEST_URI']​:包含当前请求的 URI(Uniform Resource Identifier),包括路径和查询字符串。
  10. $_SERVER['HTTP_HOST']​:包含当前 HTTP 请求中的主机名和端口。

例如

http://www.xxx.com/index.php?p=123&q=456

结果:$_SERVER[“QUERY_STRING”]=“p=123&q=456”

parse_str​ 是 PHP 中的一个内置函数,其主要作用是将查询字符串解析为变量并放入当前作用域中。它的一般语法如下:

1
parse_str(string $str, array &$arr)
  • $str​ 是包含查询字符串的字符串。
  • $arr​ 是一个可选参数,如果提供,解析后的变量将被存储在该数组中。

parse_str​ 的主要作用包括:

  1. 解析查询字符串:它可以将一个包含查询字符串的字符串(通常是 URL 查询参数部分)分解成单个变量,并将它们放入当前作用域中。
  2. 设置变量:它可以根据查询字符串中的名称和值创建对应的 PHP 变量,并将这些变量的值设置为查询字符串中的值。
  3. 处理表单数据:通常在处理 HTML 表单提交时,parse_str​ 可以用来将表单数据提取为 PHP 变量,以便在服务器端进行处理。

示例:

1
2
3
4
5
$queryString = "name=John&age=30&city=NewYork";
parse_str($queryString);
echo $name; // 输出 "John"
echo $age; // 输出 "30"
echo $city; // 输出 "NewYork"

需要注意的是,parse_str​ 在将查询字符串解析为变量时,会覆盖已经存在的同名变量,因此要小心不要覆盖现有的变量。此外,为了安全性考虑,应该谨慎处理用户提供的查询字符串,以防止潜在的安全风险。

extract($_POST)​:这一行代码尝试将所有 POST 参数提取为独立的 PHP 变量。这将 POST 参数的名称作为变量名,其值作为变量的值,使它们在当前作用域中可用。

1
?_POST[flag1]=8gen1&_POST[flag2]=8gen1

"_POST[flag1]=8gen1&_POST[flag2]=8gen1"​ 和 "flag1=8gen1&flag2=8gen1"​ 这两种查询字符串的格式之间确实有区别。

  1. _POST​ 部分:在 "_POST[flag1]=8gen1&_POST[flag2]=8gen1"​ 查询字符串中,参数名被放在 _POST​ 数组中,这似乎模拟了 POST 请求中的参数结构,但实际上并不是标准的查询字符串格式。这样的查询字符串通常需要额外的处理来提取其中的参数。

    extract​ 函数是用来将数组的键作为变量名,键对应的值作为变量的值,引入到当前作用域中。它可以处理任何关联数组,包括 $_POST​ 数组。

  2. 标准查询字符串:而 "flag1=8gen1&flag2=8gen1"​ 查询字符串是标准的查询字符串格式,其中参数名和值直接由等号分隔,并且参数之间使用和号 &​ 分隔。这是常见的 URL 查询字符串格式,适用于 GET 请求和一般的数据传递。

众所周知,php变量名只能是数字字母下划线,传进去的变量名会将 , +,.,[转换成_,若变量中有.,[替换成_后,之后的字符不会再被替换成_,例如a[b.c a_b.c,所以我们post传入504[SYS.COM=123&echo $flag;,得到flag

7.hextobin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?php 
# -*- coding: utf-8 -*-
# @Author: ShawRoot
# @Date: 2022-07-21 08:42:23
# @link: https://shawroot.cc

highlight_file(__FILE__);
$args1 = $_GET['args1'];
$args2 = $_GET['args2'];
$args3 = $_GET['args3'];
$evil = $args1.'('.$args2.')('.$args3.')'.';';
$blacklist = '/system|ass|exe|nc|eval|copy|write|\.|\>|\_|\^|\~|%|\$|\[|\]|\{|\}|\&|\-/i';
if (!preg_match($blacklist,$evil) and !ctype_space($evil) and ctype_graph($evil))
{
echo "<br>".$evil."<br>";
eval($evil);
}

?>

8.sql注入时

1
select * from `123456789`;
1
2
3
4
5
6
7
8
9
10
11
关于在这里使用 ` 而不是 ’ 的一些解释:

两者在linux下和windows下不同,linux下不区分,windows下区分。

单引号 ’ 或双引号主要用于 字符串的引用符号

反勾号 ` 数据库、表、索引、列和别名用的是引用符是反勾号 (注:Esc下面的键)

有MYSQL保留字作为字段的,必须加上反引号来区分!!!

如果是数值,请不要使用引号

9.Referer伪造访问来源

伪造访问来源,Referer协议就是告诉服务器我从哪里来。所以抓包修改。

10.User-Agent伪造访问浏览器

User-Agent​协议来伪造访问工具为 Syclover 浏览器,这个协议就是告诉服务器我是用什么访问的 .修改 User-Agent 为User-Agent: Syclover

11.X-Forwarded-For伪造ip访问

伪造本地ip 127.0.0.1,所以我们可以利用X-Forwarded-For​协议来伪造只需要在 header 添加 X-Forwarded-For:127.0.0.1

12.参数类型只能为数字,怎么办?如何绕过

答案:在变量名前面加空格。

? num**=2;var_dump(scandir(chr(47)))**

? num**=var_dump(scandir(chr(47)))**

为什么要在num前加一个空格?

答:假如waf不允许num变量传递字母,可以在num前加个空格,这样waf就找不到num这个变量了,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。

PHP的字符串解析特性是什么?

答: PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:1.删除空白符 2.将某些字符转换为下划线(包括空格)【当waf不让你过的时候,php却可以让你过】

13./ ‘’ flag被过滤

1
2
3
4
5
? num=print_r(scandir('/')); 

chr(47)='/'

? num=print_r(scandir(chr(47)));

①:scandir — 列出指定路径中的文件和目录。

②:var_dump()/print_r — 函数用于输出变量的相关信息。

③:chr(47) — 是指ascii码为47的字符为/而/在linux中指的是根目录。

总结:输出并列出指定路径中的文件和根目录。

1
2
3
4
5
6
? num=print_r(file_get_contents('/flagg'));

其中/flagg 用chr进行绕过

? num=print_r(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));

①:file_get_contents() — 函数是用于将文件的内容读入到一个字符串中的首选方法。如果操作系统支持,还会使用内存映射技术来增强性能。

②:var_dump()/print_r — 函数用于输出变量的相关信息。

④:chr(47).chr(102).chr(49).chr(97).chr(103).chr(103) — 指的是ascii码转换为f1agg

总结:输出calc.php文件中f1agg目录的字符串内容。

14.SQL注入 select * from ‘admin’ where password=md5($pass,true)

ffifdyop

129581926211651571912466741651878684928

下面就说明这道题的sql注入的原理,我们可以直接看到后台里面的php判断代码:

1
2
3
4
5
6
7
8
9
<!-- $password=$_POST['password'];
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";
$result=mysqli_query($link,$sql);
if(mysqli_num_rows($result)>0){
echo 'flag is :'.$flag;
}
else{
echo '密码错误!';
} -->
   从代码很容易知道,用mysqli_num_rows()函数来判断是否sql语句查询结果有返回值,那么重点就是那条sql语句。那么唯一可以sql注入的地方就是md5($password,true)这个地方。

   那么首先介绍一下md5这个函数。

 语法  
              md5(string,raw)

content: ffifdyop
hex: 276f722736c95d99e921722cf9ed621c
raw: ‘or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c
string: ‘or’6]!r,b
这里需要注意的是,当raw项为true时,返回的这个原始二进制不是普通的二进制(0,1),而是 ‘or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c 这种。

   上面的’ffifdyop‘字符串对应的16位原始二进制的字符串就是”    'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c    “  。  '  \  '后面的3个字符连同'  \  '算一个字符,比如’    \xc9    ‘,所以上述一共16个。当然,像’    \xc9    ‘这种字符会显示乱码。