C、C++和Java安详编码实践提示与能力
副标题#e#
对付所有范例情况中的开拓人员来说,安详性正成为一个越来越重要的主题,即便已往一直认为安详性不成问题的嵌入式系统也是如此。本文将先容几种范例的编码裂痕,指出裂痕是什么、如何低落代码被进攻的风险、如何更好地找出代码中的此类缺陷。
注入进攻
通过将信息注入正在运行的流程,进攻者可以危害历程的运行状态,以反射到开拓人员无法掩护的某种最终方针。譬喻,进攻者大概会通过仓库溢出(stack corruption)将代码注入历程,从而执行进攻者选定的代码。另外,进攻者也大概实验将数据注入数据库,供未来利用;或将未受掩护的字符串注入数据库查询,获取比开拓人员更多的信息。无论出于奈何的目标,注入老是一件坏事,老是需要审慎看待的。
最恶劣的注入进攻形式也许是代码注入——将新代码置入正在运行的历程的内存空间,随后指示正在运行的历程执行这些代码。此类进攻假如乐成,则险些可以举办任何操纵,因为正在运行的历程完全被挟制,可执行进攻者但愿执行的任何代码。
此类进攻最著名的示例之一就是 Windows 动画光标进攻,这正是本文要接头的模式。进攻者操作一个简朴的 Web 页面将形式不妥的动画光标文件下载到查察者的 PC 中,导致欣赏器挪用此动画光标,动画光标挪用时大概产生任意代码的注入。实际上,这是一个完美的进攻载体:因为它不要求对被进攻呆板的任何实际会见、最终用户基础意识不到任何大概产生的贫苦;另外,假如进攻结果的恶意也是适度的,则对最终用户的外部影响险些是零。
思量示例 1(a),虽然,这改写自 Windows 进攻,它组成了此类进攻载体的基本。这里的开拓人员对付传入流的靠得住性做出了根基的假设。信任流和并相信一切都没问题。利用基于仓库的将被非串形化(deserialized)的范例挪用函数,未知数据流和代码注入必定会在某个时间点呈现。
(a)
void LoadTypeFromStream(unsigned char* stream, SOMETYPE* typtr)
{
int len;
// Get the size of our type's serialized form
memcpy(&len, stream, sizeof(int));
// De-serialize the type
memcpy(typtr, stream + sizeof(int), len);
}
(b)
void foo(unsigned char* stream)
{
SOMETYPE ty;
LoadTypeFromStream(stream, &ty);
}
(c)
void LoadTypeFromStream
(unsigned char* stream, SOMETYPE* typtr)
{
int len;
// Get the size of our type's serialized form
memcpy(&len, stream, sizeof(int));
// GUARD
if( len < 0 || len > sizeof(SOMETYPE) )
throw TaintedDataException();
// De-serialize the type
memcpy(typtr, stream + sizeof(int), len);
}
示例1 注入进攻。
这是奈何产生的?假设您挪用示例 1(b)中的函数。我们就获得了一个易于操作的进攻载体。这里的问题在于 SOMETYPE 在编译时的巨细是牢靠的。假设此范例在内存中利用 128 个字节暗示。再假设您构建传入流时,使前 4 个字节(要非串形化的内容的长度)的读数为 256。此刻,您没有查抄正在处理惩罚的内容的有效性,而是将 256 个字节复制到了仅为 128 个字节的保存仓库空间内。
思量到宣布模式仓库的典范机关,您显然碰着了贫苦。查察仓库,相识原因地址。每个被挪用的函数城市将其当地数据布设到仓库的一个帧内,凡是是通过在输入时从仓库指针减去当地数据的已知巨细(加上处理惩罚挪用链自己所需的任何打点数据)实现的。编译器发出的抱负函数 prolog(伪代码)如下所示:
.foo
sub sp, 128 ; sizeof SOMETYPE
随后,对可操作函数的挪用应如下所示:
push sp ; push the SOMETYPE
local variable
push ap ; push the stream
pointer (comes from 1st argument)
call LoadTypeFromStream
ret
#p#副标题#e#
在挪用 foo() 时,挪用方将流地点以及返回地点(作为利用挪用指令或平台上可用的同等部门的隐式结果)压入仓库,使仓库内容中有 128 个字节是为我们的范例保存的,且紧邻返回给 foo() 挪用方的返回地点,拜见图 1。
此刻,LoadTypeFromStream 执行,并将 256 个字节写入所提供的地点,也就是在我们挪用函数之前仓库指针(SP)的值。这会包围应该利用的 128 个字节(本例中位于地点 0x1000 处),加上随后的 128 个字节,包罗传入的参数指针、返回地点以及仓库中随后 128 个字节内存储的其他任何信息。
那么进攻者奈何操作这样的裂痕呢?并不简朴,需要颠末重复的试错。实际上,进攻者要布置进攻,使包围的返回地点将节制权移交给进攻者,而非预期挪用方函数。因而,进攻者需要精确相识要操作哪些数据布局,这样的数据布局在要进攻的任意版本的操纵系统或应用措施上有多大、周边有哪些内容(以便正确设定伪造的返回地点)、如何有意义地插入足够的信息以使返回地点和其他结果可以或许实现某种恶意操纵。
这一切做起来并不简朴,但多种多样的进攻表白,老是有人有太多的空闲时间。
#p#分页标题#e#
应如何防御此类进攻?这是一次进攻照旧多重进攻?所写入的代码是否真的像这里所显示的这样鸠拙?现代编译器是否会对仓库帧机关做一些非凡处理惩罚,以制止此类问题?
总而言之,恍惚处理惩罚就便是没有防止。我们都认识到,措施员将进攻预想得越简朴,进攻呈现的大概性就越高。然而,即即是巨大的代码,若未举办公道防止,也早晚会受到进攻。这种操作被污染的数据流和很是根基的缓冲溢出裂痕的进攻,多年以来这一直是热门的研究课题,但每年仍然会呈现大量此类进攻。
防御此类进攻的结果甚微,因为进攻形式巨大——留意您的数据假设。只要在示例1(a)中添加一行简朴的代码,就会使其越发安详,拜见示例1(c)。显然,跟着流交互变得越发巨大,掩护的要求也随之巨大化,但根基上说代码注入是编码中“不行饶恕”的纰谬,因为防御它的要领是那样普及和简朴。
SQL 注入
另外还存在其他一些范例的 SQL 注入,大概会赐与数据库为中心的应用措施造成严重的问题。在某些环境下,进攻者只是实验会见更多的内容。在另一些环境下,进攻者存眷的则是在数据库中存储新信息,以便使应用措施从此在不知情的前提下利用此类信息,入侵最终用户的会话。
基于查询的进攻存眷的是一种普遍应用的反模式,利用字符串串联构建查询。这种范例的裂痕经常呈此刻面向 Web 的应用措施中,在所有常用页面产物——包罗 PHP、ASP、JSP 等及其后备节制器逻辑中同样常见。
这种裂痕的焦点是开拓人员利用直接查询执行,而非操作查询筹备来运行数据库交互。思量以下登录验证查询示例:
SELECT ID FROM USERS WHERE NAME= ‘user’ AND PWD=’password’
用户将看到一个简朴的 HTML 表单,该表单包括两个输入框并利用了这种反模式。从表单传入的参数(无论所接头的页面产物是奈何吸收到这些参数的)都将通过串联直接代入查询的字符串形式。
思量进攻者提供的一组参数:
NAME: x
PWD: x’ OR ‘1’ = ‘1
运行串联,功效将获得被操作的查询:
SELECT ID FROM USERS WHERE NAME=
‘x’ AND PWD=’x’ OR ‘1’ = ‘1’
假如登录仅查抄该语句执行乐成与否(而未思量功效行),进攻者即可迅速得到该应用措施所处理惩罚的任意用户记录可提供的任领悟见权限。许多应用措施的用户表的第一行都是为超等用户保存的,进攻此类应用措施垂手可得。
操作未审慎处理惩罚数据库语句内代入字符串的应用措施,进攻者可实现多种其他形式的进攻。这种反模式极为常见(拜见最近的 Microsoft 通告和其他内容相识其普遍性),缓解要领也同样简朴,并可置于根基数据库 API 之中:利用筹备好的语句而非字符串串联。
譬喻,思量示例2 中的错误实现。此函数严格遵循反模式,还通过抛出包括传入(未过滤)数据(即用户名)的异常而执行了别的一项重要的 no-no 操纵。假如以响应的形式为用户泛起此数据,您就很大概碰着某些恶意操作,出格是大概遭遇跨站剧本进攻。
public void validateUser(String user, String pwd, Connection db)
throws InvalidUserException
{
Statement stmt = null;
ResultSet rs = null;
try
{
// Create the statement
stmt = db.createStatement();
String sql = "select id from users where user='" + user +
"' and pwd='" + pwd + "'";
// Execute it, process the result
rs = stmt.executeQuery(sql);
if( rs == null || rs.next() == null )
throw new InvalidUserException(user);
}
catch( SQLException e )
{
throw new InvalidUserException(user);
}
finally
{
try { if( rs != null ) rs.close(); } catch( Exception e ) { }
try { if( stmt != null ) stmt.close(); } catch( Exception e ) { }
}
}
示例2 错误实现。
为了批改此代码,不该动态构建 SQL 查询,而是直接构建筹备好的语句,并利用它来取代传入参数。
我们将筹备的语句会为参数保存空间,而且不易受此类进攻操作,原因就在于它的词汇方面并不像字符串串联那样懦弱。
思量以下语句(筹备该语句的目标与前面提到的串联字符串沟通):
SELECT ID FROM USERS WHERE USER=?
AND PWD=?
#p#分页标题#e#
我利用这个筹备好的语句代入了 user 和 pwd 参数的传入数据。假如我们将之前被操作的字符串作为输入,功效将是查询代入进程堕落,因为不能将包括单引号等非凡字符的参数提供应筹备好的查询。
其他大概呈现的操作也能在差异阶段捕获到,但如示例3 所示,新实现的建设与原实现一样简朴,但安详性要高得多(我们也从抛出的异常中删除了用户名,这样可以制止在未颠末滤的环境下将其果真给挪用方的危险)。
public void validateUser(String user, String pwd, Connection db)
throws InvalidUserException
{
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
// Prepare the statement, rather than concatenating it
String sql = "select id from users where user=? and pwd=?");
stmt = db.prepareStatement(sql);
// Substitute our incoming parameters into the query
stmt.setString(1, user);
stmt.setString(2, pwd);
// Execute the query and process the results as before
rs = stmt.executeQuery();
if( rs == null || rs.next() == null )
throw new InvalidUserException();
}
catch( SQLException e )
{
throw new InvalidUserException();
}
finally
{
try { if( rs != null ) rs.close(); } catch( Exception e ) { }
try { if( stmt != null ) stmt.close(); } catch( Exception e ) { }
}
}
示例3 示例2 的较为安详的版本。
总体而言,无论是处理惩罚查询照旧DML,在处理惩罚来自最终用户的数据时,始终应利用筹备好的语句来操作数据库自己内置的过滤息争析成果。
跨站点剧本进攻(XSS)
在早期的欣赏器版本中,对付JavaScript 施加的第一项限制就是为页面内容成立一种界线,使一个站点提供的一个框架内执行的剧本无法会见其他站点提供的框架中的内容。因而,跨站点剧本进攻这种进攻模式存眷的是使来自一个站点(进攻者站点)的剧本可以或许会见其他站点的内容(譬喻,用户的银行账户站点)。
为此,用户凡是一定要会见一个恶意或不行信的网站,而社会工程的浩瀚试验已经显示,用户大概会被最离奇的站点吸引。
在此类裂痕中,最常见的形式就是简朴的反射裂痕,在一次处事器请求中将未颠末滤的 HTML 参数(凡是是表单参数)反射给用户。这种进攻载体的尺度形式首先是通过搜索引擎功效页面显示出来的,凡是会在页面标题中反射用户的查询要害词。假如未颠末滤,这种反射回的查询要害词很大概包括一些编码不妥的 HTML 标志,但可被吸收方欣赏器表明为有效的 HTML。
实际上,未颠末滤的传入数据的任何反射城市造成问题,因为 XSS数量和种类始终在增加,拜见示例4。
public void doGet(HttpServletRequest req, HttpServletResponse res)
{
string title = req.getParameter("searchTerm");
res.getOutputStream().write(title.getBytes("UTF-8"));
}
示例4 未颠末滤的传入数据自己就存在问题。
XSS反射的表示十分简朴,而办理此问题的要领也极为简朴——将从传入请求中读取的一切内容编码,之后再回发给欣赏器即可。尽量我们在这里的示例中利用了Java,但包罗HTML编码机制的所有常见页面产物均可用以制止此类裂痕。譬喻,下面这条 ASP 语句就大概被操作:
Response.Write Request.Form("username""
反之,以下语句则不能被操作:
Response.Write Server.HTMLEncode( Request.Form("username"))
尽量仍然没有内置工具可用于执行尺度转换,但也可在 Java 中举办雷同转换,以制止此类操作。也就是说,可轻松编写一个雷同的 String 转换措施。对付寻找“现成”产物包的用户,JTidy 项目(jtidy.sourceforge.net)是一个抱负的起点。
其他越发巨大的 XSS 表示形式以未过滤用户输入的耐久存储为中心,此类输入内容会在随后用于提供响应内容。这是一类更难以诊断的 XSS,因为进攻模式不只依赖于所存储的未颠末滤的用户输入,还依赖于从此对其他用户可用的存储数据。
在早期Web成长阶段,不行信任的论坛提供的软件包出格易受此类进攻模式的影响。即即是此刻,在数据库(或文件)中存储未颠末滤的传入数据并随后将所存储的数据发送给用户的应用措施也易于受到此类耐久形式的 XSS 的进攻。
#p#分页标题#e#
同样,办理要领很是简朴,只需通过编程,在存储信息之前将信息编码或在将信息从耐久存储发送给用户之前编码即可。总而言之,在存储之前编码数据老是越发安详,这种方法可以担保将来对此类数据的利用免遭XSS 进攻。
查找裂痕
本文先容的问题的规避要领易于实现,但对付实验节制现有代码库或新建代码库的安详性的开拓人员或开拓组织而言,所面对的最大挑战就是找到裂痕地址。毫无疑问,可以操作手动代码查抄的要领,但我可以确定地说,围坐在桌边、查察大量代码并实验找出大概成为裂痕的内容绝非乐事。
静态源代码阐明为此类问题提供了一种可行的办理方案,这种要领存眷代码中现有的潜在裂痕或弱点,而不是像传统安详性应用措施或渗透测试东西那样实验找到现有裂痕或进攻载体 。操作 SCA 东西可显著淘汰查找并缓解此类问题所需的时间和事情量。
今朝有多种开源和贸易东西可用,别离具有差异的成果。Klocwork(我今朝效力的企业)就提供了这样一种贸易静态源代码阐明产物套件,主要存眷 C、C++ 和 Java,为开拓人员提供了快速、精确的运行缺陷和安详裂痕阐明,而且可以或许集成在您所选择的 IDE 之中。
文章来历:http://www.ddj.com/cpp/210602504