国外博客最近公布了关于wordpress主题的一个0day,这些主题都使用了timthumb.php这个文件,该文件用于处理图片的显示效果等,原文地址可以参见:http://sebug.net/vuldb/ssvid-20811 。这里具体分析下漏洞的成因,其实在国外这篇文章也有分析,只是不是很清晰
appdir
PHP
WordPress
漏洞成因
大家可以首先打开这个链接,版本控制,可以看到:
r143 ,stronger website domain checks (don’t allow http://wordpress.com.hacker.com/)
作者在这里标明了已经修复了这个漏洞,那么我们就对比下r143和r142两个版本的区别。
对比分析这两个版本,发现结果如下:
1.foreach ($allowedSites as $site) {
2. if (strpos (strtolower ($url_info['host']), $site) !== false) {
3. $isAllowedSite = true;
4.}
5.
6.foreach ($allowedSites as $site) {
7. if (strpos (strtolower ($url_info['host'] . ‘/’), $site) !== false) {
8. $isAllowedSite = true;
9.}
对了一个’/',在匹配的时候限制了白名单域名只能在地址的最后面,这样就限制了可以在任意的域名前面加上白名单的域名,举个例子来说,我们假如有域名xyz.com,那么我们可以随便添加二、三级域名。先看看timthumb限制了哪些域名。
1.// external domains that are allowed to be displayed on your website
2.$allowedSites = array (
3. ‘flickr.com’,
4. ‘picasa.com’,
5. ‘blogger.com’,
6. ‘wordpress.com’,
7. ‘img.youtube.com’,
8. ‘upload.wikimedia.org’,
9.);
这样的话我们就可以添加blogger.com.xyz.com,成功绕过白名单的检测。看到这里我不禁想到我上次分析的百度贴吧flash过滤机制研究,也存在一定的问题,因此在匹配或者是搜索的时候需要特别注意。
利用
如果大家看了原文的留言,别人给出了利用方法(不过我没域名测试),这里我们还是对源码进行一下分析。
首先是引入url的地方:
1.// sort out image source
2.$src = get_request (‘src’, ”);
3.if ($src == ” || strlen ($src) <= 3) {
4. display_error (‘no image specified’);
5.}
get_request函数:
1./**
2. *
3. * @param [HTML_REMOVED] $property
4. * @param [HTML_REMOVED] $default
5. * @return [HTML_REMOVED]
6. */
7.function get_request ($property, $default = 0) {
8. if (isset ($_GET[$property])) {
9. return $_GET[$property];
10. } else {
11. return $default;
12. }
13.}
然后是文件检查:
1.// clean params before use
2.$src = clean_source ($src);
3.// get mime type of src
4.$mime_type = mime_type ($src);
5.// used for external websites only
6.$external_data_string = ”;
7.// generic file handle for reading and writing to files
8.$fh = ”;
9.// check to see if this image is in the cache already
10.// if already cached then display the image and die
11.check_cache ($mime_type);
其中cleansource函数中调用checkexternal函数实现了写文件操作。
1.**
2. *
3. * @global array $allowedSites
4. * @param string $src
5. * @return string
6. */
7.function check_external ($src) {
8. global $allowedSites;
9. // work out file details
10. $fileDetails = pathinfo ($src);
11. $filename = ‘external_’ . md5 ($src); //注意这个地方是文件生成后的文件名
12. $local_filepath = DIRECTORY_CACHE . ‘/’ . $filename . ‘.’ . strtolower ($fileDetails['extension']);
13. // only do this stuff the file doesn’t already exist
14. if (!file_exists ($local_filepath)) {
15. if (strpos (strtolower ($src), ‘http://’) !== false || strpos (strtolower ($src), ‘https://’) !== false) {
16. if (!validate_url ($src)) {
17. display_error (‘invalid url’);
18. }
19. $url_info = parse_url ($src);
20. // convert youtube video urls
21. // need to tidy up the code
22. if ($url_info['host'] == ‘www.youtube.com’ || $url_info['host'] == ‘youtube.com’) {
23. parse_str ($url_info['query']);
24. if (isset ($v)) {
25. $src = ‘http://img.youtube.com/vi/’ . $v . ‘/0.jpg’;
26. $url_info['host'] = ‘img.youtube.com’;
27. }
28. }
29. // check allowed sites (if required)
30. if (ALLOW_EXTERNAL) {
31. $isAllowedSite = true;
32. } else {
33. $isAllowedSite = false; //注意这个地方是重点
34. foreach ($allowedSites as $site) {
35. if (strpos (strtolower ($url_info['host']), $site) !== false) {
36. $isAllowedSite = true;
37. }
38. }
39. }
40. // if allowed //判断正确了就直接写文件了。
41. if ($isAllowedSite) {
42. if (function_exists (‘curl_init’)) {
43. global $fh;
44. $fh = fopen ($local_filepath, ‘w’);
45. $ch = curl_init ($src);
46. curl_setopt ($ch, CURLOPT_TIMEOUT, CURL_TIMEOUT);
47. curl_setopt ($ch, CURLOPT_USERAGENT, ‘Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0′);
48. curl_setopt ($ch, CURLOPT_URL, $src);
49. curl_setopt ($ch, CURLOPT_RETURNTRANSFER, TRUE);
50. curl_setopt ($ch, CURLOPT_HEADER, 0);
51. curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
52. curl_setopt ($ch, CURLOPT_FILE, $fh);
53. curl_setopt ($ch, CURLOPT_WRITEFUNCTION, ‘curl_write’);
54. // error so die
55. if (curl_exec ($ch) === FALSE) {
56. unlink ($local_filepath);
57. touch ($local_filepath);
58. display_error (‘error reading file ‘ . $src . ‘ from remote host: ‘ . curl_error ($ch));
59. }
60. curl_close ($ch);
61. fclose ($fh);
62. } else {
63. if (!$img = file_get_contents ($src)) {
64. display_error (‘remote file for ‘ . $src . ‘ can not be accessed. It is likely that the file’);
65. }
66. if (file_put_contents ($local_filepath, $img) == FALSE) {
67. display_error (‘error writing temporary file’);
68. }
69. }
70. if (!file_exists ($local_filepath)) {
71. display_error (‘local file for ‘ . $src . ‘ can not be created’);
72. }
73. $src = $local_filepath;
74. } else {
75. display_error (‘remote host “‘ . $url_info['host'] . ‘” not allowed’);
76. }
77. }
78. } else {
79. $src = $local_filepath;
80. }
81. return $src;
82.}