php wp foo,【翻译】WordPress WPDB SQL注入攻击(技术文档)
WordPress 4.8.3中修復了一個重要的SQL注入漏洞。漏洞是今年9月20日由Hacker-One報告的。本文主要講了漏洞的技術細節和解決方法。
升級到最新版本
網站管理員應該升級WordPress到4.8.3版本并更新重寫 $wpdb的所有插件,就可以預防此類問題。為客戶機升級wp-db.php,可能需要修改一些防火墻規則,比如攔截 %s 和其他sprintf() 值。
插件開發者應該?
一般來說,檢查所有的查詢段的用戶輸入,不要將用戶輸入傳遞到查詢端,如:
$where?=?$wpdb->prepare("?WHERE?foo?=?%s",?$_GET['data']);$query?=?$wpdb->prepare("SELECT?*?FROM?something?$where?LIMIT?%d,?%d",?1,?2);
$where?=?"WHERE?foo?=?'"?.?esc_sql($_GET['data'])?.?"'";$query?=?$wpdb->prepare("SELECT?*?FROM?something?$where?LIMIT?%d,?%d",?1,?2);
以上兩種方法從概念是講都是不安全的,
安全的查詢方法為:
$where?=?"WHERE?foo?=?%s";$args?=?[$_GET['data']];$args[]?=?1;$args[]?=?2;$query?=?$wpdb->prepare("SELECT?*?FROM?something?$where?LIMIT?%d,?%d",?$args);
漏洞
WPDB::prepare源碼(4.8.2之前版本):
public?function?prepare(?$query,?$args?)?{
if?(?is_null(?$query?)?)
return;
//?This?is?not?meant?to?be?foolproof?--?but?it?will?catch?obviously?incorrect?usage.
if?(?strpos(?$query,?'%'?)?===?false?)?{
_doing_it_wrong(?'wpdb::prepare',?sprintf(?__(?'The?query?argument?of?%s?must?have?a?placeholder.'?),?'wpdb::prepare()'?),?'3.9.0'?);
}
$args?=?func_get_args();
array_shift(?$args?);
//?If?args?were?passed?as?an?array?(as?in?vsprintf),?move?them?up
if?(?isset(?$args[0]?)?&&?is_array($args[0])?)
$args?=?$args[0];
$query?=?str_replace(?"'%s'",?'%s',?$query?);?//?in?case?someone?mistakenly?already?singlequoted?it
$query?=?str_replace(?'"%s"',?'%s',?$query?);?//?doublequote?unquoting
$query?=?preg_replace(?'|(?
$query?=?preg_replace(?'|(?
array_walk(?$args,?array(?$this,?'escape_by_ref'?)?);
return?@vsprintf(?$query,?$args?);}
用vsprintf(與sprintf基本等價)的值來替換占位符;
用str_replace來適當地引用占位符;
如果傳遞了一個參數,而這個參數是數組的話,用數組的值來替換參數。
這意味著調用$wpdb->prepare($sql, [1, 2]) 與調用$wpdb->prepare($sql, 1, 2)是等價的。
最初報告的漏洞依賴與下面的服務端代碼:
$items?=?implode(",?",?array_map([$wpdb,?'_real_escape'],?$_GET['items']));$sql?=?"SELECT?*?FROM?foo?WHERE?bar?IN?($items)?AND?baz?=?%s";
$query?=?$wpdb->prepare($sql,?$_GET['baz']);
漏洞利用vsprintf的特征來允許絕對引用參數,例子如下:
vsprintf('%s,?%d,?%s',?["a",?1,?"b"]);?//?"a,?1,?b"vsprintf('%s,?%d,?%1$s',?["a",?2,?"b"]);?//?"a,?2,?a"
注意%n$s不會讀下一個參數,但是會讀第n個位置的參數??梢愿鶕@個特性在原始查詢中進行注入。假設傳遞下面的信息到請求中:
$_GET['items']?=?['%1$s'];$_GET['baz']?=?"test";
查詢會變成?SELECT?*?FROM?foo?WHERE?bar?IN?('test')?AND?baz?=?'test';我們成功地改變了查詢的本意。
There’s?one?other?key?piece?of?information?that?the?original?report?included?to?change?this?into?a?full-blown?SQL?Injection.?sprintf?also?accepts?another?type?of?parameter:?%c?which?acts?like?chr()?and?converts?a?decimal?digit?into?a?character.?So?now,?the?attacker?can?do?this:
最初的漏洞報告中還有一個關鍵的信息是可以把這個變成成熟的SQL注入。Sprintf也會接受其他類型的參數,%c與chr()含義相同,可以把小叔變成字符,所以攻擊者可以:
$_GET['items']?=?['%1$c)?OR?1?=?1?/*'];$_GET['baz']?=?39;
ASCII表中39代表’(單引號),所以查詢就變成了這樣:
SELECT?*?FROM?foo?WHERE?bar?IN?('')?OR?1?=?1?/*'?AND?baz?=?'test';
注入就完成了。
這個過程看似很復雜,需要提前準備好輸入的參數等,實際上該漏洞也存在于核心文件/wp-includes/meta.php 中:
if?(?$delete_all?)?{
$value_clause?=?'';
if?(?''?!==?$meta_value?&&?null?!==?$meta_value?&&?false?!==?$meta_value?)?{
$value_clause?=?$wpdb->prepare(?"?AND?meta_value?=?%s",?$meta_value?);
}
$object_ids?=?$wpdb->get_col(?$wpdb->prepare(?"SELECT?$type_column?FROM?$table?WHERE?meta_key?=?%s?$value_clause",?$meta_key?)?);}
最早的補丁
WordPress4.8.2發布時,就包含上述問題的一個補丁。補丁整個包含在WPDB::prepare()中,補丁只加了1行代碼:
$query?=?preg_replace(?'/%(?:%|$|([^dsF]))/',?'%%\\1',?$query?);
這1行代碼做了2件事情。1是移除了除%d,%s,%F之外的sprintf令牌,因為漏洞是依賴%c的,因此使漏洞無效。2是移除了位置替換的能力,即%1$s這樣的參數就無效了。
這引起了開發人員的不滿,因為WordPress在官方文檔中說只能使用%d,%s,%F。即使官方文檔是這么寫的,上百萬的第三方查詢代碼都使用了前面的語法規則。
WordPress的回應是“won’t fix, sorry”,并以安全為由拒絕提供更多細節。
最初補丁的第一個問題
漏洞是傳遞用戶輸入到prepare的服務端。最初漏洞的POC是這樣的,安全查詢代碼如下:
$db->prepare("SELECT?*?FROM?foo?WHERE?name=?'%4s'?AND?user_id?=?%d",?$_GET['name'],?get_current_user_id());
4.8.2中的變化是%4s會被重寫成%%4s,也就是說%d會反彈到$_GET['name'],給了攻擊者用戶id的機會。這可以被用來進行權限提升攻擊。
WordPress的回應是:“thank you, we don’t support that”。
全面攻擊
然后作者設計了一個不同的POC,利用另一個重要的事實來證明該漏洞不是%1$s,而是傳遞用戶輸入到prepare查詢端。Meta.php文件代碼如下:
if?(?$delete_all?)?{
$value_clause?=?'';
if?(?''?!==?$meta_value?&&?null?!==?$meta_value?&&?false?!==?$meta_value?)?{
$value_clause?=?$wpdb->prepare(?"?AND?meta_value?=?%s",?$meta_value?);
}
$object_ids?=?$wpdb->get_col(?$wpdb->prepare(?"SELECT?$type_column?FROM?$table?WHERE?meta_key?=?%s?$value_clause",?$meta_key?)
);}
輸入:
$meta_value?=?'?%s?';$meta_key?=?['dump',?'?OR?1=1?/*'];
產生了下面的查詢:
SELECT?type?FROM?table?WHERE?meta_key?=?'dump'?AND?meta_value?=?''?OR?1=1?/*'
成功注入了核心文件,$meta_value 和 $meta_key都來自于用戶的輸入。會產生下面的賦值子句:
AND?meta_value?=?'?%s?'
未引用的%s通過prepare被引用的%代替,第二次調用->prepare()把clause變成AND meta_value = ' '%s' ' ,就可以注入了。
作者強調該漏洞不能在WPDB::prepare() 修復,但是是meta.php中的問題??梢酝ㄟ^預防double prepare calls緩解該漏洞。但是不能修復原始漏洞。
簡單補丁
簡單的補丁不是傳遞用戶輸入的$query參數到meta.php中的WPDB::prepare()。傳遞用戶輸入到$query是錯誤的。
緩解補丁
下一步是在預查詢中引用占位符,然后在執行查詢前恢復占位符,這個補丁已經有了。基本上,補丁會修改WPDB::prepare()把隨機字數穿用%占位符代替,比如:
$query?=?str_replace('%',?"{$this->placeholder_escape}",?$query?);
然后,在WPDB::_do_query()去除占位符來恢復最初的用戶的用戶輸入。
我仍然認為傳遞用戶輸入到prepare的查詢端是存在潛在危險的而且是不安全的。即使你解決了已知的安全漏洞,double-preparing字符串是及其危險的,因為prepare的結果會傳遞到另一個。
正確的補丁
正確的補丁應該是拋棄整個prepare機制。像正常的查詢那樣,返回一個statement或query的對象,或者直接執行查詢。這種方式可以預防double prepare字符串的情況。值得一提的是這將會是WP的主要變化。其他平臺已經有成功的先例了,比如PHPBB經歷了同樣的事情,從大規模的SQL注入漏洞到幾乎沒有SQL注入漏洞。也不需要很快解決,可以與現有的API并行處理,慢慢地去取代老的API。目前的系統在設計之處就是不安全的,但這也不意味著會經常被黑,但是你要盡量去讓它不被黑。最好使用默認安全的設計,并讓不安全成為特例。其中最佳的實踐方法是使用PDO/MySQLi和real prepared statements。這些變化并不能防止被誤用,但是會讓誤用變得更難。
https://blog.ircmaxell.com/2017/10/disclosure-wordpress-wpdb-sql-injection-technical.html
總結
以上是生活随笔為你收集整理的php wp foo,【翻译】WordPress WPDB SQL注入攻击(技术文档)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工行房贷宽限期
- 下一篇: 邮政信用卡取现额度是多少