原文地址:https://zhuanlan.zhihu.com/p/26134332
[作者:phithon,現就職于長亭科技,參與Pwnhub的開發與運營,長期關注并筆耕于安全編碼、代碼審計等方向]
Pwnhub( pwnhub | Beta ),中文解釋‘破解中心’,諧音胖哈勃,一個以各種安全技術為內容的競賽平臺。從去年12月初上線到現在,已經穩定運行了4個月,主辦了10場比賽。作為一篇安全技術向的文章,我們不談情懷,不談運營,單說說Pwnhub的開發,我遇到過哪些(安全)暗坑,以及怎么去規避。
本文主要從微信模塊安全、API安全、權限控制、前端安全、商城邏輯安全、運維安全等幾個方面來介紹Django的安全編碼。
由于我們公司的 Web 技術棧是 Vue+Django,采用純 SPA 異步通信模式,選型上自然沒有任何難度,很快前后端配合開始了工作。
微信模塊安全
Django框架的優勢之一就是其自帶的用戶模塊,我們可以用少量代碼搭建整體用戶邏輯,包括用戶登錄、找回密碼、權限控制等。但因為Pwnhub在選型的時候就確定使用微信登錄,所以用戶邏輯方面勢必需要進行一些改造。
我的做法是前臺砍掉Django的用戶登錄大部分所有邏輯,包括:注冊、登錄、找回密碼、修改密碼,僅留下一個退出登錄,然后自己做了微信登錄相關的代碼邏輯。
任何一個與微信相關的應用,想保證與微信間的通信,需要注意以下4個變量:
- 應用ID AppID
- 應用密鑰 AppSecret
- 通信令牌 Token
- 消息加密密鑰 EncodingAESKey
其中,必須配置的前三者,后者可以不用配置;必須保密的是后三者,前者可以公開。
應用IDAppID和應用密鑰AppSecret是我們服務器和微信服務器通信的憑證。舉個例子,因為Pwnhub需要掃碼登錄,所以我得向微信服務器提出這個需求,并拿到二維碼圖片。那么,微信服務器信任這個請求的前提就是我需要帶上access_token訪問,而access_token是一個臨時憑證(有效期2小時),拿到這個臨時憑證的方式就是利用AppID和AppSecret。
AppID是告訴微信服務器“我是誰”,而AppSecret是證明我是“這個人”的憑證,微信確認了這二者的對應關系后,就會給我發放一張有效期為2小時的“通行證”。以后出入微信服務器都應該帶上這個通行證,也就是access_token;如果過期,用相同的方法再次獲取。
通信令牌Token是微信服務器訪問我們服務器的憑證。這就涉及到HTTP協議的特點,因為HTTP協議是一個無狀態的協議,所以,雖然我向微信服務器證明了我是誰,但是并不能建立一個“通信通道”,所以當微信服務器需要訪問我的時候,我還需要驗證他的身份。
Token相當于是一個簽名密鑰,微信服務器在訪問我之前,會生成一個隨機數,并和當前時間戳、Token一起使用sha1簽名后發送給我的服務器。我需要對其進行校驗,如果簽名不正確的會就返回錯誤。
消息加密密鑰EncodingAESKey是微信后來推出,以確保通信信息不被中間人竊取的方式。因為微信公眾號使用者的服務器不一定配置了https,所以通信過程中可以選擇使用EncodingAESKey來加密消息內容。
舉個例子,用戶、Pwnhub與微信服務器間通信流程如下:

這個過程存在著幾個經常被開發者忽視的問題:
- AppSecret、Token泄露
- timestamp不進行驗證,導致的信息永不過期
- 服務端沒有配置HTTPS,且EncodingAESKey不設置,導致中間人劫持
AppSecret、Token泄露是老問題了,很多開發者將自己的代碼直接上傳到github等第三方代碼托管平臺,而沒有刪除此類敏感信息;即使用戶及時發現并刪除了信息,git的特性也導致攻擊者可以在以往提交的記錄中找到這些信息。好在AppSecret和Token都能重置,一旦發現有泄露的情況,第一步應該是重置這兩個配置。
時間戳不驗證,這個問題在微信公眾號示例代碼中就存在:
<?php
...
public function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData, &$msg)
{
if (strlen($this->encodingAesKey) != 43) {
return ErrorCode::$IllegalAesKey;
}
$pc = new Prpcrypt($this->encodingAesKey);
//提取密文
$xmlparse = new XMLParse;
$array = $xmlparse->extract($postData);
$ret = $array[0];
if ($ret != 0) {
return $ret;
}
if ($timestamp == null) {
$timestamp = time();
}
$encrypt = $array[1];
$touser_name = $array[2];
//驗證安全簽名
$sha1 = new SHA1;
$array = $sha1->getSHA1($this->token, $timestamp, $nonce, $encrypt);
$ret = $array[0];
if ($ret != 0) {
return $ret;
}
$signature = $array[1];
if ($signature != $msgSignature) {
return ErrorCode::$ValidateSignatureError;
}
$result = $pc->decrypt($encrypt, $this->appId);
if ($result[0] != 0) {
return $result[0];
}
$msg = $result[1];
return ErrorCode::$OK;
}
可見,驗證安全簽名相等以后就直接解密,那么,時間戳timestamp就沒有任何意義了。正確的做法應該是,在驗證簽名相等的同時,檢查當前時間和時間戳的差是否在一定范圍內。
EncodingAESKey是保證服務器和微信公眾號之間的通信不被第三方監聽。如果你得服務器沒有配置HTTPS證書,就應該正確配置該值。
除此之外,由于微信公眾號是用XML進行通信,所以難免會遇到XML安全相關的問題:雖然保證Token不泄密的情況下,第三方用戶無法對服務器進行請求;但極端情況下,我們是不應該相信任何用戶的輸入,即使該用戶是微信服務器。
XXE等XML中常見的安全問題我就不多說了,我使用Python的模塊defusedxml( defusedxml 0.5.0 )來確保XML解析時候的安全。
API安全
由于是前后端分離項目,Pwnhub前臺部分全部使用API通信。API安全是Pwnhub整體安全的重中之重,應該API的通信可以看做是動作和數據的交換,一旦有沒有配置好的部分,將可能導致水平權限繞過和信息泄露等漏洞。
Pwnhub后端使用Django-Rest-Framwork開發(后文用DRF簡稱),DRF是一個基于Django的API擴展庫,我們通過其內置的Serializer概念,即可很方便地控制Model中“我想讓用戶看到的屬性”和“用戶可以控制的屬性”。
前段時間,有個朋友來問我,DRF的Serializer會不會導致反序列化相關的漏洞。其實很多人誤會了此處Serializer的角色,雖然它有這樣一個容易被人誤解的名字,但Serializer實際作用和原生Django中的Form類似,主要作用是控制用戶輸入的內容,并進行校驗。但有一個不同點是,Serializer還能控制輸出信息的范圍。
我們可以把Serializer理解為一個Model和View之間的媒介,View將用戶輸入的信息發送給Serializer,Serializer進行篩選和校驗,成功后發送給Model,保存進數據庫;Model從數據庫中取出信息交給Serializer,Serializer進行篩選,只顯示開發者允許顯示的內容,并交給View,Views顯示出來。

在代碼中,我們通過在Serializer里定義fields,來限制允許顯示或修改的屬性:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = [
'id', # 用戶id
'username', # 用戶名
'email', # 郵箱
'password', # 密碼
'rank', # 積分
'coin', # 金幣
'mugshot', # 頭像
'tag', # 用戶擅長方向
'introduction', # 個人介紹
'signature', # 簽名
'date_joined' # 注冊時間
]
read_only_fields = [
'id',
'username',
'email',
'rank',
'coin',
'date_joined'
]
上述代碼是一個簡單的用戶Model對應的Serializer,可見,我通過定義fields,列出程序需要的有哪些字段;再通過定義read_only_fields,來限制哪些是只讀字段。
也就是說,用戶id、用戶名、郵箱、用戶積分、金幣和注冊時間是不允許修改的。
但上述代碼有一個很明顯的問題:雖然限制了哪些字段是只讀的,但沒有限制哪些字段是只寫的。
如果使用DRF的內置View,在每次創建或更新一個Model對象后,會自動顯示修改后的Serializer,也就是fields里所有的字段值。但實際上有些字段是不太適合顯示的,比如password,這個字段應該定義為“只可寫而不允許讀”:
extra_kwargs = {
'password': {'write_only': True}
}
這個細節是開發很容易忽視的,如果設計不好,將很可能導致類似前幾天Gitlab出現的任意用戶敏感信息泄露漏洞(CVE-2017-0882)。
不過,DRF里定義write_only和read_only的方式確實比較奇葩,read_only有簡單寫法read_only_fields,但write_only則沒有。
權限控制
DRF有一套自定義性很高的鑒權機制。
因為其為一個API框架,所以面向的前端可能是瀏覽器,也可能是手機APP,所以常規Web中以Cookie和Session授權的方法就不一定有效了。
DRF內置了三種授權(authentication)方法:HTTP基礎認證、Session、Token。HTTP基礎認證適合于純瀏覽器的前端環境,Session適合于瀏覽器或基于瀏覽器內核的應用程序,Token適合無瀏覽器環境的應用程序。
用戶也可以定義自己的授權方法,并任意搭配它們。舉個例子,Pwnhub現在使用的是Session進行授權,以后如果要開發手機App,可能會增加基于Token的授權方法。
通過授權方法,DRF給每個請求標記上一個用戶(未登錄的用戶被稱為匿名用戶AnonymousUser),但該用戶是否有權限訪問某個API,這就得問“許可模塊”(Permission)了。
也就是說,Authentication模塊只負責給請求“發通行證”,Permission模塊負責檢查這個“通行證”是否有權限訪問某個地點。

不同的API View可以選擇使用不同的Authentication和Permission,根據自己需要的訪問權限任意搭配,也可以使用全局默認值。Pwnhub前臺權限很簡單,用Session授權,并只有兩種Permission:登錄用戶和匿名用戶,所以配置起來也比較簡單。
另外,權限方面DRF還提供了一個更高級的模塊:Throttling。使用這個模塊,即可定義某個“用戶”訪問某個API的頻度。舉個例子,某視頻網站允許每個免費用戶每天觀看5個視頻,就需要經過如下步驟:
Authentication:Session認證 --> Permission:是否是已登錄用戶 --> Permission:是否是免費用戶 --> Throttling:是否已觀看5個視頻 --> View:返回視頻地址
當然,Pwnhub的功能暫時還沒有這么復雜。
前端安全
前端安全是不可繞過的一個話題。近年來前后端分離的開發模式一定程度上減少了后端代碼直接輸出用戶輸入導致的XSS等前端漏洞,但同時也催生了一種新的攻擊方式:在前后端沒有完全分離的情況下,將可能出現“客戶端模板注入漏洞”(Client-Side Template Injection )。
這種攻擊方式可以參考這篇文章( XSS without HTML: Client-Side Template Injection with AngularJS ),本文就不對其進行介紹了。本文從4個切入點來說說Pwnhub可能面臨的前端漏洞。
XSS in Vue
Pwnhub前端基于Vue框架,開發者可以通過數據綁定的方式,很容易地在前端顯示從后端獲取的數據,而且這個過程是相對安全的:因為在將數據輸出到模板的時候,Vue默認會將數據識別為純文本,而非HTML代碼。
但并不是所有位置的輸出都適合純文本,也不是所有程序員都能正確理解他需要編寫的邏輯。
舉個例子,如果產品經理希望用戶評論能包含圖片、超鏈接,那么前端頁面在輸出評論內容的時候就不能對其進行轉碼。在Vue中需要使用v-html指令:
<div v-for="comment in $store.state.comments" :key="comment.id">
{{ comment.username }}: <div v-html="comment.content"></div>
</div>
見上述代碼,comment.username是評論者名稱,它被包裹在雙大括號內,默認識別為純文本;而comment.content放在v-html指令中,將會被識別為HTML代碼。那么,一旦用戶提交惡意HTML評論,后端沒有處理的情況下,將造成XSS漏洞。
如果你有這個需求,可以使用我編寫的python-xss-filter( phith0n/python-xss-filter )在后端對評論內容進行處理。
XSS in Django
由于Python運行模式的特殊性(由某一入口點統一進入),上傳文件后綴通常也不會卡的太死,只要不覆蓋到程序本身的py文件,都不會有太大問題。所以,Django本身很多操作默認是不檢查后綴的,比如我們定義一個model,其中包含一個ImageField():
class Attachment(models.Model):
name = models.CharField('名稱', blank=True, null=True, max_length=256)
photo = models.ImageField('圖片', blank=True)
...
很多人認為,ImageField是Django提供的圖片字段,按理說Django自己應該已經做了細致的檢查。但實際上Django只檢查用戶上傳的文件內容是否是圖片,而不會檢查文件后綴是否是圖片后綴。
但服務器中間件和瀏覽器并不會根據文件內容來判斷一個文件的mime_type,而是根據后綴。所以,我只需要上傳一個文件內容符合GIF格式,而后綴是.html的文件,即可構造一個XSS漏洞。

訪問a.html,成功執行:

假如當前服務器支持SSI或PHP等語法,那么這個問題可能更會上升成一個服務端漏洞。
解決這個問題的方法是,在ImageField字段上增加validators,如photo = models.ImageField('圖片',validators=[check_image_extension], null=True, blank=True),check_image_extension函數里對文件后綴進行檢查即可。
CSRF漏洞
CSRF漏洞在Django中有默認的檢查:所有POST, PUT, PATCH和DELETE請求都會檢查CSRF Token。但Django對CSRF Token檢查機制是,將Cookie中的Token和表單或HTTP頭中的Cookie進行比對,以避免請求被惡意偽造。
這就和DRF的設計理念有些出入了:DRF面向的前端可以是瀏覽器,也可能不是瀏覽器,Cookie并不是一個必要的值。
所以,DRF在APIView中強制關閉了Django原生的CSRFCheck(csrf_exempt(view)):
class APIView(View):
...
@classmethod
def as_view(cls, **initkwargs):
"""
Store the original class on the view function.
This allows us to discover information about the view when we do URL
reverse lookups. Used for breadcrumb generation.
"""
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '
'as the result will be cached and reused between requests. '
'Use `.all()` or call `.get_queryset()` instead.'
)
cls.queryset._fetch_all = force_evaluation
view = super(APIView, cls).as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
# Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt.
return csrf_exempt(view)
并將CSRF檢查放在了SessionAuthentication中,TokenAuthentication和BasicAuthentication中是沒有CSRF檢查的。(當然也不需要檢查,因為Token或401賬號密碼都是攻擊者不能獲取的)
如果是開發者自己定義Authentication,那么CSRF驗證這塊就需要格外注意了。
另外,DRF只檢查登錄用戶的CSRF Token,這個特點在大部分情況下都沒有問題,因為非登錄用戶也基本不存在請求偽造的必要了。但有一種情況,就是用戶登錄的操作,我們需要手工給這些方法加上@method_decorator(ensure_csrf_cookie)用來生成CSRF Token,并進行手工檢查(如果你使用Django自帶的login view并開啟了CSRF全局校驗,就無需手工檢查了)。
Pwnhub會在前端獲取Cookie中的CSRF Token,并附加到所有HTTP請求中,以保證正常的流程不會被攔截。
Json Hijacking
后端API返回中可能包含一些敏感信息,那么,如何在前端保證這些數據不被其他人竊取?
理論上,瀏覽器的跨域原則足夠防御這一類攻擊了,但是如果后端返回的數據被瀏覽器“錯誤”地認為是一個合法的JavaScript或css數據,那么就可能造成信息泄露攻擊了,這也是Jsonp劫持等很多跨域漏洞的原理。
DRF在輸出層,提供了一個叫Renderer的概念,也就是輸出數據的渲染方式。通常在開發環境下,為了調試方便,我會使用BrowsableAPIRenderer,這個渲染方式會提供一個Web頁面用來輸入和顯示數據:

但如果需要和前端配合使用的時候,就需要用到JSONRenderer了。它會將Serializer輸出的數據轉換成Json再返回給前端框架:

見上圖,雖然實際我們需要傳遞的數據是一個數組["PWN","REVERSE","CRYPTO","MISC","WEB"],但Pwnhub對數據進行了一層包裹,那么這層包裹對改進安全性上有什么幫助?
這就涉及到Json劫持漏洞了(區分json劫持和jsonp劫持)。眾所周知,Json是可以被JavaScript原生支持的,所以,如果我們使用<script>標簽去加載我們的API返回結果,將會有一些奇妙的反應:
- 如果Json API返回的是一個對象,將拋出Uncaught SyntaxError: Unexpected token :異常,因為瀏覽器會認為它并不是一個合法的JavaScript對象(為什么?)
- 如果Json API返回的是一個數組,瀏覽器將不會拋出異常
所以,如果我返回了第二種情況,攻擊者通過對JavaScript數組對象的劫持,將可能可以竊取數組中的數據。
關于最新版瀏覽器的JSON Hijacking,可以參考這篇文章: JSON hijacking for the modern web
Django自帶的JsonResponse默認會檢查用戶傳入的數據是否是一個字典,因為只有字典對應到Json中才是對象,相對安全:
class JsonResponse(HttpResponse):
"""
An HTTP response class that consumes data to be serialized to JSON.
:param data: Data to be dumped into json. By default only ``dict`` objects
are allowed to be passed due to a security flaw before EcmaScript 5\. See
the ``safe`` parameter for more information.
:param encoder: Should be an json encoder class. Defaults to
``django.core.serializers.json.DjangoJSONEncoder``.
:param safe: Controls if only ``dict`` objects may be serialized. Defaults
to ``True``.
:param json_dumps_params: A dictionary of kwargs passed to json.dumps().
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super(JsonResponse, self).__init__(content=data, **kwargs)
如上,safe默認為True,在這種情況下,data必須為dict。
商城邏輯
2017年3月初,Pwnhub上線了一個簡單的商城功能,參與比賽的用戶可以用自己在比賽中獲得的積分兌換禮品。新功能的上線,勢必引入新的安全威脅,因為Pwnhub的商城交易純屬虛擬貨幣邏輯,并不復雜,所以除了傳統的水平權限漏洞以外,商城邏輯中最容易被忽視的漏洞其實是條件競爭。
條件競爭漏洞已經多次發生在我們周圍了,最近一次是小密圈在2017年2月下旬出現的用戶超額提現的問題( 小密圈產品事故@2017 No.2 )。
Pwnhub商城中可能出現條件競爭漏洞的地方有兩處:
- 用戶購買某商品時,余額判斷后的條件競爭
- 用戶購買多件商品時,商品數量判斷后的條件競爭
有的同學可能還不太理解一個商城購買商品的邏輯,我以Pwnhub為例畫了個簡圖:

所以,上述邏輯攻擊者有兩種攻擊方式:
- 某一個用戶同一時間對不同商品發送多次購買請求,這些請求都通過了“用戶余額判斷”,該用戶余額被扣成負數,相當于使用它沒有的錢購買了多件商品
- 不同用戶同一時間對同一商品發送多次購買請求,這些請求都通過了“商品剩余數量判斷”,結果導致商品數量被買成負數,相當于買到了無貨的商品
舉個例子,A用戶擁有10個金幣,剛好購買價值8金幣的商品B。這種情況下,A用戶開啟10個線程同時發送購買商品請求,在數據庫尚未修改的情況下,這10個請求均發現A的余額是足夠購買B商品的,然后扣除8金幣并成功下單。此時A用戶的金幣數量為10 - 8 * 10 = -70,但10個訂單均已經成功支付了。
針對類似的攻擊,大部分數據庫通常都提供了某個記錄加鎖的功能,也就是在查詢某條記錄的時候使用SELECT FOR UPDATE,在Django ORM里使用.select_for_update()語法。
with transaction.atomic():
# 獲取用戶和商品對象
user = User.objects.select_for_update().get(pk=self.request.user.pk)
gift = Gift.objects.select_for_update().get(pk=serializer.validated_data['gift'].pk)
# 檢查用戶余額和商品數量
serializer.atomic_validate(user, gift)
# 下訂單
order = serializer.save(user=self.request.user)
# 扣除用戶余額
user.coin -= order.cost
user.save()
# 扣除商品數量
gift.amount -= order.amount
gift.save()
另外,商城邏輯容易出BUG的地方還有幾個點:
- 下單和支付邏輯不同步的問題
- 購買商品數量是負數的情況
- 前端傳入商品價格的情況
第一個問題基本不存在了,因為Pwnhub是前后分離,下單操作可以在前端完成,后端只要接收到“購買(支付)”請求,就立馬扣款。
第二、三個問題,我們在Serializer中對其進行校驗即可:
class BuySerializer(serializers.ModelSerializer):
amount = serializers.IntegerField(label='數量', min_value=1, max_value=10)
class Meta:
model = models.GiftLog
fields = [
'order_no',
'gift',
'address',
'remark',
'amount',
]
read_only_fields = ['order_no']
解決第二個問題的方法是在IntegerField里增加min_value,解決第三個問題的方法是……fields中根本沒有商品價格,因為商品價格是從gift對象的price中獲取的。
上線前夕
做了那么一大堆工作,如果部署的時候稍有不慎,可能前面的安全工作都白做了。
部署的時候應該從如下三個切入點考慮:
- Django框架安全部署
- Django-Rest-Framework框架安全部署
- Web容器安全部署
Django框架安全部署
Django考慮的非常周到,在項目上線前,我們可以通過執行./manage.py check --deploy命令來查看可能存在的安全問題:

可見,默認生成的項目,存在以上安全問題,在部署前需要解決:
- SECURE_HSTS_SECONDS 是否開啟HSTS頭,強制HTTPS訪問
- SECURE_CONTENT_TYPE_NOSNIFF 是否輸出nosniff頭,以防止類型混淆類漏洞
- SECURE_BROWSER_XSS_FILTER 是否輸出x-xss-protection頭,讓瀏覽器強制開啟XSS過濾
- SECURE_SSL_\REDIRECT 是否讓HTTP的請求強制跳轉到HTTPS
- SESSION_COOKIE_SECURE 是否將Cookie設置為Secure(不允許在HTTP中傳輸)
- CSRF_COOKIE_SECURE 是否將CSRF Token Cookie設置為Secure(不允許在HTTP中傳輸)
- CSRF_COOKIE_HTTPONLY 是否將CSRF Token Cookie設置為HTTP ONLY
- X_FRAME_OPTIONS 是否返回X-FRAME-OPTIONS: DENY頭,以防止被其他頁面作為框架加載
- DEBUG 是否開啟調試模式
其中,DEBUG是必須要關閉的。其他的選項主要看你對安全的要求了,例如如果你得頁面不強制使用HTTPS,那么其中的一些防止中間人劫持的選項就沒必要設置。
另外,上線前,最好修改后臺地址,如果你不需要使用Django自帶的后臺,可以在INSTALLED_APPS里將其移除。SECRET_KEY重新進行生成,不要和開發、測試環境相同:openssl rand 10 -out .secret。
Django-Rest-Framework框架安全部署
前文已經說了,開發環境中DRF通常使用BrowsableAPIRenderer,但在部署到生產環境后,最好將這個Renderer移除,只保留我們需要用到的JSONRenderer:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
#'rest_framework.renderers.BrowsableAPIRenderer',
),
...
}
因為攻擊者可以利用BrowsableAPIRenderer輸出的表單和返回結果,很容易地進行測試和攻擊,也可能是DRF本身出現的前端安全漏洞(如XSS),會影響到你。
另外,DRF自帶一個用戶登陸View( 4 - Authentication and permissions - Django REST framework ),如果你不想用戶被爆破的話,也請檢查自己的urls.py,是否添加了這個View。
Web容器安全部署
這個就是常規話題了,按照服務器正常加固方法加固即可。
因為Pwnhub部署在Docker中,所以我們需要用Nginx在前端進行轉發,但Django中有些需要獲取IP的方法(Django有個配置叫INTERNAL_IPS,如果訪客IP在這個配置項中,將可能可以享受一些DEBUG模式才有的功能),我使用了HTTP_X_FORWARDED_FOR這個頭,那么在Nginx中就必須覆蓋用戶提交的頭,否則就有可能造成任意IP偽造漏洞。
總結
作為一個小團隊,雖然很難像微軟等大公司這樣去實現完整的SDL,但是針對Pwnhub這樣小規模的項目,通過學習上述的安全編碼知識,我們也能最大程度保障這個項目不被惡意攻擊。如果大家有一些安全編碼相關的想法,歡迎與我們討論。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/262/
暫無評論