國外博客最近公布了關于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.}