2013-10-28 31 views
1

(我正在問這個問題(並回答它),以便獲取一些(希望有用的)信息,因爲我無法使用搜索引擎輕鬆找到這些信息,但請隨時回答並添加有用的信息:-)。 )如何停止HTTP(和rfc822,電子郵件)標題注入?

HTTP標頭如何在Python中被轉義/引用?

和/或他們如何驗證以確保它們不包含任何上下文轉義值?

換句話說,我們應該如何處理HTTP標頭,cgi.escapeurllib.quote方法(和消毒)對HTML和URL所做的工作?這可以用來防止HTTP header injection和類似的漏洞。

例如...

我們讓用戶提供一個應該重定向到的URL。我們希望防止注入攻擊(其中SQL injection是衆所周知的)。如果我們決定重定向using the Location: header,我們如何避免使用用戶提供的URL來阻止HTTP頭注入(爲了討論)安全問題(涉及暗中自動轉發到用戶可以選擇的域中的URL)或者檢測它是否包含對HTTP有危險的值)?現在

# on a "posix sh"-like command-line... 
# ...(it contains a malicious HTTP value) 
$ redirect_to 'http://example.com'"\r\n"'Set-Cookie: malicious=value' 

,在我們實施redirect_to命令Python代碼,我們要像上面要麼被轉義輸入(使之無害化),或者是一個錯誤。我們該怎麼做?

回答

1

如果被包括在報頭字段參數(例如filename parameter of the Content-Disposition header),它可以被用email.utils.encode_rfc2231編碼(由these specifications,它定義了rfc2231 encoding的變化作爲約束的)的輸入數據。

如果它是而不是包含一個頭字段參數,那麼看起來這個方法不能使用。在這種情況下,最安全的賭注可能只是不包括輸入,如Julian Reschke wrote;然而,如果你堅持包括輸入,你可能想嘗試以下方法之一:

(這可能是不安全的,因爲HTTP is not a MIME-compliant protocol,所以除非the MIME-Version header is used(並且可能即使它被使用?),這些方法可能不支持HTTP正確工作)

一種方式...

要做到這一點,雖然它可能不是完全萬無一失(編輯:它是萬無一失(按使用時本身);它接受\r\n\r\n,終止標題並啓動正文!因此,需要處理\r\n,除非前面有非\r/\n空格(如製表符或空格),否則將使用email.header模塊。這是專爲rfc822 headers編輯:但(看起來,因爲電子郵件包過去是幾個單獨的模塊(example))not for HTTP headers!),所以似乎是工作的工具。這Header類是用於編碼標頭,而不是完整的Header-Name: value,因此是這個工作的候選人(我們想要voda或逃生值只有)。

(提示:與其他MIME格式(編輯工作時,許多email模塊中的工具也很方便:可能還有MIME-等)的東西,尤其是這麼過的東西cgi模塊中,cgi.FieldStorage用於HTTP的形式解析)

然而,僅email.header會如果輸入似乎惡意引發錯誤(似乎包含另一個(嵌入式)報頭);但是,它似乎不會處理無效的輸入通過轉義(如果不是這樣的話,請在註釋中更正)。 (該charset參數應逃脫報頭片段,返回有效輸入,但是,它可能沒有與用戶代理(電子郵件,HTTP等這樣良好的相容性);見here編輯:許多HTTP用戶代理支持(不一定是charset參數編碼爲email.header.Header類(這似乎使用除了RFC2231編碼一些特定的MIME-編碼),但)的rfc5987編碼)

例:

import email.header 
import re 

def check_string_for_rfc822_header(s): 
    wip_header_component = str(email.header.Header(s)) 
    if re.search(r'(\r?\n[\S\n\r]|\r[\S\r])', wip_header_component): 
     raise Exception 
    else: 
     return wip_header_component 

# testing... 
>>> check_string_for_rfc822_header("aaa") 
"aaa" 
>>> check_string_for_rfc822_header("a\r\nb") 
"a\r\nb" 
>>> check_string_for_rfc822_header("a\r\nb: c") 
<error> 

的另一種方式...

要做到這一點,它似乎只是remove \r and \n characters(每個單獨但是;不要僅僅刪除整個字符串\r\n,因爲這會在單獨發生時仍然會使這些未轉義,並且許多(大多數?)HTTP utils將分別接受它們中的每一個!)。同樣,我們可以通過替換\r\n\r\n來替換它們,它們自己用空格(這是擺脫標題的方式;參見the standard)作爲前綴。

但是,此方法沒有考慮標準的細節(例如rfc822標頭must be ACSII),這些標準可能會被單獨利用。

例子:

def remove_linebreakers(s): 
    return s.replace("\n", "").replace("\r", "") 

# or... 
import re 

def remove_linebreakers(s): 
    re.sub(r'[\n\r]', '', s) 


# testing... 
>>> remove_linebreakers("aaa") 
"aaa" 
>>> remove_linebreakers("a\r\nb") 
"ab" 
>>> remove_linebreakers("a\r\nb: c") 
"ab: c" 

總之...

第一種方式似乎更好,但僅用於驗證(不轉義),除非它是一個參數值,在這種情況下逃脫它使用email.utils.encode_rfc2231

例子:

# if we are not working with a header param value, the following... 
# ...raises email.errors.HeaderParseError if input is poisonous when in a header 
wip_header_component = str(email.header.Header('<input>')) 
header_component = (raise_error() if re.search(r'(\r?\n[\S\n\r]|\r[\S\r])', wip_header_component) else wip_header_component) 
# ...or if we *are* working with a header param value... 
email.utils.encode_rfc2231('<input>', 'UTF-8') 
1

不要逃避。只需停止處理(放下標題或整個請求)。

+0

我覺得我沒有在問題中正確解釋自己;我修改了它(和它的標題),試圖解釋更多。 – Abbafei

+0

我仍然不會生成重定向。只需返回400. –

+0

是的,我想這樣做更有意義。我使用關於處理文件名的[info I gleaned](http://stackoverflow.com/q/1361604)更新了我的答案,這是我當前的用例,後來我意識到[some of](http:// tools.ietf。org/html/rfc6266)有關這些主題的標準,[由作者撰寫](http://tools.ietf.org/html/rfc5987)由你們:-) – Abbafei