一次性付费进群,长期免费索取教程,没有付费教程。
教程列表见微信公众号底部菜单
进微信群回复公众号:微信群;QQ群:460500587
微信公众号:计算机与网络安全
ID:Computer-network
源代码审计是一种提高软件安全性的方法。我们将了解一些代码审计的方法和工具,从人工到借助工具,最后会通过一些实例来看到如何通过现有的工具进行审计,以及这些工具如何帮助我们发现并消除漏洞。需要注意的是,在源代码审计阶段,一些产品安全设计上的问题可能已经较难被发现和修改,代码审计更多是发现代码实现上的错误和遗漏。
在代码量可控的情况下,并且没有很好的工具支持时,我们可以考虑通过总结经验,自己实现相关的检查工具。以找到未正确过滤数据类型的漏洞为例,定位问题可以简单分为以下几个步骤:1)标注出高危行为入口函数,2)标注数据获取来源,3)标注数据过滤函数。之后回朔调用过程,添加简单的逻辑。
1、自动化审计产品
对于代码量大的产品,人工审计明显不能满足需求,这时需要寻求工具的帮助。代码分析技术由来已久,1976年科罗拉多大学的Lloyd D.Fosdick和Leon J.Osterweil在ACM Computing Surveys上发表了著名的Data Flow Analysis in Software Reliability论文,其中就提到了数据流分析、状态机系统、边界检测、数据类型验证、控制流分析等技术。随着计算机语言的不断演进,代码分析技术也在日趋完善。目前有数量众多的开源和商业源码审计工具建立在这些分析技术之上,其中包括Foritify,Coverity,FindBugs等。这些自动化的代码审计产品能够满足对审计量和强度的要求,并且大多提供和开发环境相整合的组件,可以融入到日常的开发和编译过程当中。工具不可避免会产生漏报和误报,在处理工具生成的报告时,需要人工对生成的结果进行验证。
2、Coverity
Coverity是斯坦福大学Dawson Engler教授和他的三个学生发起完成的代码审计工具,目前为包括NASA等500多个公司或政府部门提供服务。选择其为例的原因有:1)在具体的使用中感觉误报率相对较少,2)目前有免费针对开源代码审计的服务,3)可以找到相关的原型论文。Coverity支持的语言有C,C++和Java,支持发现的问题类型包括:
resources leaks
dereferences of NULL pointers
incorrect usage of APIs
use of uninitialized data
memory corruptions
buffer overruns
control flow issues
error handling issues
incorrect expressions
concurrency issues
insecure data handling
unsafe use of signed values
use of resources that have been freed
以C/C++为例,Coverity的分析引擎Prevent包含以下的组件和功能参见表1。
表1 Coverity的分析引擎Prevent包含的组件和功能
如果想看到实际Coverity运行的结果,可以在scan.coverity.com上浏览一些针对开源软件进行的审计结果,目前已经有为数众多的开源软件通过Coverity提高代码安全质量。图1是某个开源项目的输出结果。
图1 输出结果
https://scan.coverity.com/o/oss_success_stories中包含甄选出通过Coverity发现的漏洞列表,如图2所示。
图2 漏洞列表
点击View Defect可以看到详细问题描述,现在以一个curl(一个利用URL语法在命令行方式下工作的开源文件传输工具)中的漏洞作为例子进行查看针对某问题的具体展示结果:
https://scan.coverity.com/o/oss_success_stories/46,如图3所示。
图3 详细问题描述
这是一个存在于curl中的信息泄露漏洞。由于没有对SMB服务端返回的长度数据进行合法性检查,通过构造特定网络包数据,将导致curl客户端发送出非预期的数据,导致信息泄露。该漏洞也已被修复,分配的CVE编号为CVE-2015-3237,公告见http://curl.haxx.se/docs/adv_20150617B.html。下面是Coverity给出的函数中漏洞触发流程。
696 *done = true;
697 break;
698
699 default:
700 smb_pop_message(conn);
701 return CURLE_OK; /* ignore */
702 }
703
704 smb_pop_message(conn);
705
706 return CURLE_OK;
707 }
708
709 static CURLcode smb_request_state(struct connectdata *conn, bool *done)
710 {
711 struct smb_request *req = conn->data->req.protop;
712 struct smb_header *h;
713 enum smb_req_state next_state = SMB_DONE;
714 unsigned short len;
715 unsigned short off;
716 CURLcode result;
717 void *msg = NULL;
718
719 /* Start the request */
< 1. Condition "req->state == SMB_REQUESTING", taking false branch
720
721 if(req->state == SMB_REQUESTING) {
722 result = smb_send_tree_connect(conn);
723 if(result) {
724 connclose(conn, "SMB: failed to send tree connect message");
725 return result;
726 }
727
728 request_state(conn, SMB_TREE_CONNECT);
729 }
730
731 /* Send the previous message and check for a response */
result = smb_send_and_recv(conn, &msg);
< 2. Condition "result", taking false branch
732 if(result && result != CURLE_AGAIN) {
733 connclose(conn, "SMB: failed to communicate");
734 return result;
735 }
< 3. Condition "!msg", taking false branch
737 if(!msg)
738 return CURLE_OK;
739
740 h = msg;
< 4. Switch case value "SMB_DOWNLOAD"
switch(req->state) {
742 case SMB_TREE_CONNECT:
743 if(h->status) {
744 req->result = CURLE_REMOTE_FILE_NOT_FOUND;
745 if(h->status == smb_swap32(SMB_ERR_NOACCESS))
746 req->result = CURLE_REMOTE_ACCESS_DENIED;
747 break;
748 }
749 req->tid = smb_swap16(h->tid);
750 next_state = SMB_OPEN;
751 break;
752
753 case SMB_OPEN:
754 if(h->status) {
755 req->result = CURLE_REMOTE_FILE_NOT_FOUND;
756 next_state = SMB_TREE_DISCONNECT;
757 break;
758 }
759 req->fid = smb_swap16(((struct smb_nt_create_response *)msg)->fid);
760 conn->data->req.offset = 0;
761 if(conn->data->set.upload) {
762 conn->data->req.size = conn->data->state.infilesize;
763 Curl_pgrsSetUploadSize(conn->data, conn->data->req.size);
764 next_state = SMB_UPLOAD;
765 }
766 else {
767 conn->data->req.size =
768 smb_swap64(((struct smb_nt_create_response *)msg)->end_of_file);
769 Curl_pgrsSetDownloadSize(conn->data, conn->data->req.size);
770 next_state = SMB_DOWNLOAD;
771 }
772 break;
773
774 case SMB_DOWNLOAD:
775
< 5. Condition "h->status", taking false branch
776
777 if(h->status) {
778 req->result = CURLE_RECV_ERROR;
779 next_state = SMB_CLOSE;
780 break;
}
<<< CID 1299430: Insecure data handling TAINTED_SCALAR
<<< 6. Function "Curl_read16_le" returns tainted data.
<< 7. Assigning: "len" = "Curl_read16_le", which taints "len".
781
782 len = Curl_read16_le(((unsigned char *) msg) +
783 sizeof(struct smb_header) + 11);
785 off = Curl_read16_le(((unsigned char *) msg) +
sizeof(struct smb_header) + 13);
<< 8. Casting narrower unsigned "len" to wider signed type "int" effectively tests its lower bound.
< 9. Condition "len > 0", taking true branch
<< 10. Checking lower bounds of unsigned scalar "len" by "len > 0".
785
if(len > 0) {
<<< CID 1299430: Insecure data handling TAINTED_SCALAR
<<< 11. Passing tainted variable "len" to a tainted sink.
786
787 result = Curl_client_write(conn, CLIENTWRITE_BODY,
788 (char *)msg + off + sizeof(unsigned int),
789 len);
790 if(result) {
791 req->result = result;
792 next_state = SMB_CLOSE;
793 break;
794 }
795 }
796 conn->data->req.bytecount += len;
797 conn->data->req.offset += len;
798 Curl_pgrsSetDownloadCounter(conn->data, conn->data->req.bytecount);
799 next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD;
break;
800
801 case SMB_UPLOAD:
802 if(h->status) {
803 req->result = CURLE_UPLOAD_FAILED;
804 next_state = SMB_CLOSE;
805 break;
806 }
807 len = Curl_read16_le(((unsigned char *) msg) +
808 sizeof(struct smb_header) + 5);
809 conn->data->req.bytecount += len;
810 conn->data->req.offset += len;
811 Curl_pgrsSetUploadCounter(conn->data, conn->data->req.bytecount);
812 if(conn->data->req.bytecount >= conn->data->req.size)
可以看到在满足以上代码数字开头的条件后,即可触发该漏洞,简化的流程如下:
smb_request_state→ len = Curl_read16_le(…)→ Curl_client_write(…, len)
Curl客户端从服务端接到的数据包中提取出len的值,之后没有经过检查便将len作为长度参数传入Curl_client_write函数:
CURLcode Curl_client_write(struct connectdata *conn,
int type,
char *ptr,
size_t len)
Curl_client_write会将ptr指针后的len字节数据发送给服务端,即使len超过预期的数据包长度。
Curl新版本中针对这个漏洞的补丁中增加了对len的长度检查,计算后的值不能超过已收到的smb数据包的边界(smb->got)。(http://curl.haxx.se/CVE-2015-3237.patch):
---
lib/smb.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/lib/smb.c b/lib/smb.c
index 8cb3503..d461a71 100644
--- a/lib/smb.c
+++ b/lib/smb.c
@@ -781,13 +781,19 @@ static CURLcode smb_request_state(struct connectdata *conn,
bool *done)
len = Curl_read16_le(((unsigned char *) msg) +sizeof(struct smb_header) + 11);
off = Curl_read16_le(((unsigned char *) msg) +sizeof(struct smb_header) + 13);
if(len > 0) {
- result = Curl_client_write(conn, CLIENTWRITE_BODY,
- (char *)msg + off + sizeof(unsigned int),
- len);
+ struct smb_conn *smbc = &conn->proto.smbc;
+ if(off + sizeof(unsigned int) + len > smbc->got) {
+ failf(conn->data, "Invalid input packet");
+ result = CURLE_RECV_ERROR;
+ }
+ else
+ result = Curl_client_write(conn, CLIENTWRITE_BODY,
+ (char *)msg + off + sizeof(unsigned int),
+ len);
if(result) {
req->result = result;
next_state = SMB_CLOSE;
break;
}
--
至此,从这个漏洞分析过程,我们可以了解到Coverity审计的实际效果。
微信公众号:计算机与网络安全
ID:Computer-network
【推荐书籍】