函数缺陷原理
先看一段代码
这段代码的逻辑大概是这个样子:
这个代码的类中有render()方法,其中render方法在index.html网页上输出了一个链接,这个过程是通过模板实现的,模板首先通过了escape方法进行过滤,其中链接的参数是通过getNexSlideUrl()获得的,往上看这个函数,这个函数是通过get方法获得一个参数然后通过filter_var()函数进行过滤作为函数的返回值。
escape方法
其中escape方法的原理是用htmlspecialchars()实现的
htmlspecialchars :(PHP 4, PHP 5, PHP 7)
功能 :将特殊字符转换为 HTML 实体
定义 :string htmlspecialchars ( string
$string
[, int$flags
= ENT_COMPAT | ENT_HTML401 [, string$encoding
= ini_get("default_charset") [, bool$double_encode
= TRUE ]]] )& (& 符号) =============== & " (双引号) =============== " ' (单引号) =============== ' < (小于号) =============== < > (大于号) =============== >
filter_var 函数
filter_var 函数来过滤 nextSlide 变量,且用了 FILTER_VALIDATE_URL 过滤器来判断是否是一个合法的url,具体的 filter_var 定义如下:
filter_var : (PHP 5 >= 5.2.0, PHP 7)
功能 :使用特定的过滤器过滤一个变量
定义 :mixed filter_var ( mixed
$variable
[, int$filter
= FILTER_DEFAULT [, mixed$options
]] )
demo
为了能更清晰的看清楚代码逻辑,下面写一个demo
<?php
$url = filter_var($_GET['url'],FILTER_VALIDATE_URL);
var_dump($url);
echo "<br>";
$url = htmlspecialchars($url);
//$url=urldecode($url);
var_dump($url);
echo "<br>";
//$url=urldecode($url);
echo "<a href='$url'>Next slide</a>";
?>
那么针对这两个函数的过滤我们采用JavaScript伪协议进行绕过
payload:
?url=javascript://comment%250aalert(1)
其中//在javascrpt里面是换行符,url传入后端的时候会被第一次url解码,此时%25被解码成%,和后面0a拼接成%0a,当点击链接的时候,会进行第二次url解码,其中%0a会被解码成换行符,alert(1)就被换到了下一行,逃过了注释符号,也被浏览器解析。
实战利用
环境搭建:Anchor 0.9.2
在该版本中,当用户访问一个不存在的URL链接时,程序会调用404模板,而这个模板则存在XSS漏洞,具体代码如下:
//anchor-cms-0.9.2\themes\default\404.php
<?php theme_include('header'); ?>
<section class="content wrap">
<h1>Page not found</h1>
<p>Unfortunately, the page <code>/<?php echo current_url(); ?></code> could not be found. Your best bet is either to try the <a href="<?php echo base_url(); ?>">homepage</a>, try <a href="#search">searching</a>, or go and cry in a corner (although I don’t recommend the latter).</p>
</section>
<?php theme_include('footer'); ?>
然后跟进一下current_url()函数
//anchor-cms-0.9.2\anchor\functions\helpers.php
function current_url() {
return Uri::current();
}
然后跟进一下Uri类的current()函数
//anchor-cms-0.9.2\system\uri.php
class Uri {
public static function current() {
if(is_null(static::$current)) static::$current = static::detect();
return static::$current;
}
}
然后跟进一下detect()函数
public static function detect() {
// create a server object from global
$server = new Server($_SERVER);
$try = array('REQUEST_URI', 'PATH_INFO', 'ORIG_PATH_INFO');
foreach($try as $method) {
// make sure the server var exists and is not empty
if($server->has($method) and $uri = $server->get($method)) {
// apply a string filter and make sure we still have somthing left
if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {
// make sure the uri is not malformed and return the pathname
if($uri = parse_url($uri, PHP_URL_PATH)) {
return static::format($uri, $server);
}
// woah jackie, we found a bad'n
throw new ErrorException('Malformed URI');
}
}
}
throw new OverflowException('Uri was not detected. Make sure the REQUEST_URI is set.');
}
该方法会获取 $_SERVER 数组中的 'REQUEST_URI' 、'PATH_INFO', 、'ORIG_PATH_INFO' 三个键的值(下图第3-4行代码),如果存在其中的某一个键,并且符合 filter_var($uri, FILTER_SANITIZE_URL) 和 parse_url($uri, PHP_URL_PATH) ,则直接将 $uri 传入 static::format 方法。
$_SERVER["REQUEST_URI"]函数
预定义服务器变量的一种,所有$_SERVER开头的都叫做预定义服务器变量 REQUEST_URI的作用是取得当前URI,也就是除域名外后面的完整的地址路径
例如。当前页面是http://www.zixueku.com/plus/search.php?kwtype=0&keyword=php&searchtype=titlekeyword
echo $_SERVER["REQUEST_URI"];
结果就为:plus/search.php?kwtype=0&keyword=php&searchtype=titlekeyword
$_SERVER["PATH_INFO"]函数
http://www.test.com/index.php/foo/bar.html?c=index&m=search
我们可以得到 $_SERVER['PATH_INFO'] = ‘/foo/bar.html’
跟进format方法
public static function format($uri, $server) {
// Remove all characters except letters,
// digits and $-_.+!*'(),{}|\\^~[]`<>#%";/?:@&=.
$uri = filter_var(rawurldecode($uri), FILTER_SANITIZE_URL);
// remove script path/name
$uri = static::remove_script_name($uri, $server);
// remove the relative uri
$uri = static::remove_relative_uri($uri);
// return argument if not empty or return a single slash
return trim($uri, '/') ?: '/';
}
过滤函数解释
rawurldecode函数
(PHP 4, PHP 5, PHP 7, PHP 8)
rawurldecode — 对已编码的 URL 字符串进行解码
说明
rawurldecode(string
$str
): string返回字符串,此字符串中百分号(
%
)后跟两位十六进制数的序列都将被替换成原义字符。
public static function remove($value, $uri) {
// make sure our search value is a non-empty string
if(is_string($value) and strlen($value)) {
// if the search value is at the start sub it out
if(strpos($uri, $value) === 0) {
$uri = substr($uri, strlen($value));
}
}
return $uri;
}
public static function remove_script_name($uri, $server) {
return static::remove($server->get('SCRIPT_NAME'), $uri);
}
/
public static function remove_relative_uri($uri) {
// remove base url
if($base = Config::app('url')) {
$uri = static::remove(rtrim($base, '/'), $uri);
}
// remove index
if($index = Config::app('index')) {
$uri = static::remove('/' . $index, $uri);
}
return $uri;
}
没有针对XSS攻击进行过滤,导致攻击十分容易,我们来看看XSS攻击具体是如何进行的。
http://localhost/anchor/index.php/<script>alert('www.sec-redclub.com')</script>
。根据上面的分析,当我们访问这个并不存在的链接时,程序会调用404模板页面,然后调用 current_url 函数来获取当前用户访问的文件名,也就是最后一个 / 符号后面的内容,所以最终payload里的 <script>alert('www.sec-redclub.com')</script>
部分会嵌入到 <code>
标签中,造成XSS攻击
filter_var函数缺陷(CTF例题,附源码)
参考资料
先知社区-红日团队