目標(biāo)
在日常測(cè)試工作中,經(jīng)常會(huì)有api接口的測(cè)試,除了正向流程的測(cè)試之外,我們經(jīng)常還需要覆蓋一些異常情況。
例如:
- 不合法字符串
- 字符串超長(zhǎng)
- 應(yīng)該是數(shù)字類型的,傳入了字母
- 參數(shù)為空
- 傳入了中文,標(biāo)點(diǎn)符號(hào)等
- sql注入等等
事實(shí)上,我們組的接口測(cè)試demo框架中,在dataprovider中也經(jīng)常能夠看到諸如下面的例子。
@DataProvider(name = "testIllegalName")
public static Object[][] testIllegalName(){
return new Object[][]{
//name
{null, 400, "域名為空或者域名非法"},
{"", 400, "域名為空或者域名非法"},
{"abcdefghijilmnopqrstu", 400, "域名為空或者域名非法"},
{" ", 400, "域名為空或者域名非法"},
{"12", 400, "域名為空或者域名非法"},
{"-12", 400, "域名為空或者域名非法"},
{"0.2", 400, "域名為空或者域名非法"},
{"abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcd.com", 400, "域名為空或者域名非法"},
{"zxq.qa.com", 400, "域名為空或者域名非法"},
{"zxq_qa.com", 400, "域名為空或者域名非法"},
};
}
此處是看看接口在傳入非期望值的時(shí)候,能不能夠很好的處理類似請(qǐng)求。
除此以外,還有一些和業(yè)務(wù)場(chǎng)景強(qiáng)相關(guān)的值類型,比如網(wǎng)絡(luò)測(cè)試的時(shí)候,我們會(huì)關(guān)心cidr的格式;計(jì)費(fèi)測(cè)試的時(shí)候,又特別關(guān)注數(shù)字的類型。
一方面,給每個(gè)接口增加類似的異常接口測(cè)試相對(duì)比較無趣;另一方面,我們作為人,考慮問題,不管是開發(fā)還是測(cè)試,都難免掛一漏萬,有一些邊邊角角的case沒能考慮到。
既然如此,我們能否統(tǒng)一抽象出來一種接口異常測(cè)試的框架,自動(dòng) 注入各種類型的異常,然后將凡是服務(wù)沒有捕獲的,拋出trace, exception 的,記錄下請(qǐng)求的payload,為后續(xù)驗(yàn)證覆蓋提供支撐。
原理
主要使用了模糊測(cè)試技術(shù)(fuzz testing, fuzzing)。其核心思想是自動(dòng)或半自動(dòng)的生成隨機(jī)數(shù)據(jù)輸入到一個(gè)程序中,并監(jiān)視程序異常,如崩潰,斷言(assertion)失敗,以發(fā)現(xiàn)可能的程序錯(cuò)誤,比如內(nèi)存泄漏。(摘抄之維基百科)
簡(jiǎn)單的模糊測(cè)試隨機(jī)輸入數(shù)據(jù),而更加高效的模糊測(cè)試,需要理解對(duì)象結(jié)構(gòu)或者協(xié)議。通過向數(shù)據(jù)內(nèi)容,結(jié)構(gòu),消息,序列中引入一些異常,來人為的構(gòu)造聰明的模糊測(cè)試。
如果你持續(xù)關(guān)注文件系統(tǒng)或內(nèi)核技術(shù),你一定注意過這樣一篇文章:Fuzzing filesystem with AFL。Vegard Nossum 和 Quentin Casasnovas 在 2016 年將用戶態(tài)的 Fuzzing 工具 AFL(American Fuzzing Lop)遷移到內(nèi)核態(tài),并針對(duì)文件系統(tǒng)進(jìn)行了測(cè)試。
結(jié)果是相當(dāng)驚人的。Btrfs,作為 SLES(SUSE Linux Enterprise Server)的默認(rèn)文件系統(tǒng),僅在測(cè)試中堅(jiān)持了 5 秒鐘就掛了。而 ext4 堅(jiān)持時(shí)間最長(zhǎng),但也僅有 2 個(gè)小時(shí)而已。(https://zhuanlan.zhihu.com/p/28828826)
所以基于此,在api接口測(cè)試中引入模糊測(cè)試?yán)碚撋弦彩强尚械模沂怯行У摹?/p>
舉例
經(jīng)過一番調(diào)研和搜索之后,發(fā)現(xiàn)了以下這個(gè)項(xiàng)目在接口fuzz測(cè)試中,有比較好的上手體驗(yàn)。
pip install PyJFuzz
git clone https://github.com/dzonerzy/PyJFAPI.git
我對(duì) PyJFAPI 稍微進(jìn)行了一些修改,包括日志記錄,以及異常判斷的地方,只記錄服務(wù)器返回500錯(cuò)誤的情況等。
首先需要準(zhǔn)本一個(gè)請(qǐng)求的模板。
cat request.text
POST /v2.0/networks.json HTTP/1.1
Host: pubbeta1-iaas.service.163.org
X-Auth-Token: 6645b224a8314d0c89e09a011cbddf53
Content-Type: application/json
Accept: application/json
***{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}***
這里是一個(gè) post請(qǐng)求,定義了一些請(qǐng)求頭和請(qǐng)求體。星號(hào)之間的請(qǐng)求json體,為異常注入點(diǎn)。
它會(huì)自動(dòng)分析你的json格式,生成各種 payload。理論上來說,你只需要給它提供一份接口的scheme就行(要是所有接口都可以從Swagger直接導(dǎo)出那就很方便了)。
運(yùn)行:
python pjfapi.py -H pubbeta1-iaas.service.163.org -P 9797 -T request.txt
返回
我們可以手工請(qǐng)求一下這些導(dǎo)致異常的payload。
實(shí)例1:
(hzx_env) hzhuangzhexiao@pubbeta1-nova10:~$ curl -i http://pubbeta1-iaas.service.163.org:9797/v2.0/networks.json -X POST -H "X-Auth-Token: 7d52816088e84e5392019ed00c2f386f" -H "Content-Type: application/json" -H "Accept: application/json" -H "User-Agent: python-protonclient" -d '{"network": {"cidr": "20.010.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}'
HTTP/1.1 500
Content-Type: application/json;charset=ISO-8859-1
Content-Length: 9586
Date: Tue, 14 Nov 2017 05:59:42 GMT
Connection: close
An unknown exception occurred.
java.lang.IllegalArgumentException: '20.010.0.0' is not an IP string literal.
at com.google.common.net.InetAddresses.formatIllegalArgumentException(InetAddresses.java:1035)
at com.google.common.net.InetAddresses.forString(InetAddresses.java:154)
at com.netease.cns.proton.server.utils.HelperUtils.convertIpStringToInt(HelperUtils.java:107)
at com.netease.cns.proton.server.utils.HelperUtils.getIp4NetworkProtoBuf(HelperUtils.java:189)
at com.netease.cns.proton.server.utils.HelperUtils.getIp4NetworkProtoBuf(HelperUtils.java:194)
事實(shí)上,我們本來已經(jīng)對(duì)這個(gè)cidr參數(shù)進(jìn)行了一些異常值的測(cè)試。包括:
"cidr": "20.256.0.0/16" 不規(guī)范的類型
"cidr": "20.ssss.0.0/16" 部分為字符串
"cidr": "ssssss" 全為字符串
"cidr": "" 為空
等等??梢园l(fā)現(xiàn),還是有部分特殊情形沒有考慮到。
實(shí)例2
(hzx_env) hzhuangzhexiao@pubbeta1-nova10:~$ curl -i http://pubbeta1-iaas.service.163.org:9797/v2.0/security-group-rules.json -X POST -H "X-Auth-Token: af75ed821eeb4d5c9e88fb4ba804ff48" -H "Content-Type: application/json" -H "Accept: application/json" -H "User-Agent: python-protonclient" -d '{"security_group_rule": {"direction": "ingress", "protocol": "tcp", "ethertype": "IPv4", "port_range_max": "6660", "security_group_id": "48b9cc1e-53f8-4f7e-8983-bffb209153f3", "port_range_min": "80", "remote_ip_prefix": "0.0.0.0/"}}'
HTTP/1.1 500
Content-Type: application/json;charset=ISO-8859-1
Content-Length: 6310
Date: Tue, 14 Nov 2017 05:59:10 GMT
Connection: close
An unknown exception occurred.
java.lang.ArrayIndexOutOfBoundsException: 1
at com.netease.cns.proton.server.service.SecurityGroupServiceImpl.validateIpPrefix(SecurityGroupServiceImpl.java:385)
at com.netease.cns.proton.server.service.SecurityGroupServiceImpl.createSecurityGroupRule(SecurityGroupServiceImpl.java:228)
at sun.reflect.GeneratedMethodAccessor385.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
我把程序構(gòu)造的部分異常打印出來,可以看到類型還是很豐富的。
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1D, "admin_state_up"_ true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test-1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_utrue}}
{"network-": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuup": true}}
{"network": {"cidr": "20.1.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", : tru}e}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vp?-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1"?: true}}
{"network": {"cdr": "20100..0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_stap"::: true}}
{"network": {"cidr": "20.100.0.0/16", "name": hzx-vpc-test1", "admin_state_up": true}}
"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up":} true}}
{"neeeeeeeeeeeeeeeeeeeeeeeeeeeeeetwork": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up""admin_state_up""admin_state_up""admin_state_up""admin_state_up""admin_state_up""admin_s,ate_up""admin_state_up""admin_state_up": true}}
{"network": }
"n{et???: r{"cidr": "0.}0.0/-34359738368", "naD: true}}
{"network": }
{"[network": {"cidr": "20.-214748364800.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}{"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}{"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.1000.0/+.16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up""admin_state_up""admin_state_up": true}}
{"networ[k": {"cidr": "20.100.0.0/16", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/-4080", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.100.0.0/-4080", "name": "hzx-vpc-test1", "admin_state_up": true}}
{"network": {"cidr": "20.-25500.0.0/16", "name": "hzx-vpc-test1", "admin_stateeeeeeeeeeeeeeeeeeeee_up": true}}
pjfapi.py 腳本本身使用方法很簡(jiǎn)單。 -h 看下help為命令行參數(shù)的基本說明。
結(jié)論
本文簡(jiǎn)要的介紹了fuzz測(cè)試技術(shù),以及將其應(yīng)用到api接口測(cè)試中的實(shí)現(xiàn),給出了一個(gè)具體的fuzz接口測(cè)試?yán)印K灰欢軌蛲耆娲羧斯さ慕涌诋惓?,但是可以作為一個(gè)很好的補(bǔ)充。