首页 > 安全资讯 >

测试您的安全性IQ

09-02-06

 我们都喜欢通过复查代码来检查安全性错误。甚至可以说,我们对此非常擅长。我们并不是在自夸我们是最好的,但我们通常都可以迅速地找出大量错误。你能做到吗?  如果看到一个安全性错误,您能否识别出来?通过进行这一测验来评估一

 我们都喜欢通过复查代码来检查安全性错误。甚至可以说,我们对此非常擅长。我们并不是在自夸我们是最好的,但我们通常都可以迅速地找出大量错误。你能做到吗?
  如果看到一个安全性错误,您能否识别出来?通过进行这一测验来评估一下。每个代码示例都至少有一个安全漏洞。尝试找出错误并看一看您的得分。在代码后面是对这些漏洞、注释的总结,如果条件允许,还会讲述安全性开发生命周期 (SDL) 如何帮助查找这些错误。感谢 Peter Torr 和 Eric Lippert 提供输入和代码示例。
  错误 #1(C 或 C++)
[table=95%][tr][td]      void func(char *s1, char *s2) {
  char d[32];
  strncpy(d,s1,sizeof d - 1);
  strncat(d,s2,sizeof d - 1);
  ...
  }[/td][/tr][/table]
  答案:我们认为应该先来讲一讲虽然古老但却非常便于理解的缓冲区溢出。对许多人而言此代码已足够安全,因为代码使用的是受限的 strncpy 和 strncat 函数。但是,只有当缓冲区大小合适时,这些函数才是安全的,而在本例中缓冲区大小是错误的。彻头彻尾的错误。
  从技术上讲,第一个调用是安全的,但第二个调用却是错误的。strncpy 和 strncat 函数的最后一个参数是缓冲区中保留的空间量,而您刚刚通过调用 strncpy 占用了其中的一部分或全部空间。缓冲区溢出。Michael 在 2004 年发表了一篇博客文章,其中讲述了与此完全相同的错误类型。
  在 Visual C++ 2005 及以后的版本中,警告 C4996 是告诉您应将错误的函数调用替换为更安全的调用,而 /analyze 选项会发出 C6053 警告,指出 strncat 可能不会以零终止字符串。
  老实说,由于种种原因,strncpy 和 strncat(及其 "n" 同类)要比 strcpy 和 strcat(及其同类)更糟糕。首先,返回值有点多余 — 它是指向缓冲区的指针,而缓冲区可能有效,也可能无效。您没有办法知道!其次,获取正确的目标缓冲区大小的确很难。如果您找出了错误,可以给自己加一分。
  错误 #2(C 或 C++)
      main(int argc, char* argv[]) {
  char d[32];
  if (argc==2)
  strcpy(d,argv[1]);
  ...
  return 0;
  }

  答案:这是一个很棘手的错误。过去我们曾多次看到此错误被用作缓冲区溢出的示例,而大多数情况下根本无法确定代码中是否存在安全性错误。这完全取决于代码的使用方式。
  如果这是标准的 Win32 EXE,则此处不存在安全性错误,因为可能发生的最糟糕后果就是攻击您自己并自行运行代码,但这并不是安全性错误。
  现在,如果此代码位于以 SYSTEM 权限运行的 ServiceMain Windows 服务中,或者用作 Linux 应用程序 setuid 根的主函数,则此代码将变为不折不扣的安全性错误。
  让我们假设此代码被用作标记为 setuid 根的 Linux 应用程序。当应用程序由普通用户启动时,应用程序实际上以根身份运行,这意味着这是一个本地权限提升漏洞。
  如“错误 #1”中给出的代码示例所示,调用 strcpy 时会发出 C4996 警告,/analyze 将发出 C6204,指示存在潜在的缓冲区溢出。如果您回答“喔!我需要更多的上下文”,那么给自己加两分;否则不得分。
  错误 #3(可以是任何语言,示例为 C#)
      byte[] GetKey(UInt32 keySize) {
  byte[] key = null;
  try {
  key = new byte[keySize];
  RNGCryptoServiceProvider.Create().GetBytes(key);
  }
  catch (Exception e) {
  Random r = new Random();
  r.NextBytes(key);
  }
  return key;
  }

  答案:在这个糟糕的密钥生成代码中有两个错误。第一个非常明显:如果在调用从密码学角度上来说非常强大的随机数字生成器时失败,则代码会捕获此异常,然后调用根本没有任何用处而且可预测的随机生成器。如果您已发现这一点,就给自己加一分。SDL 要求使用在密码学角度上随机的数字来生成密钥。
  但还有一个错误:代码会捕获所有异常。除了极少数情况外,捕获所有异常(无论是 C++ 异常、Microsoft .NET Framework 异常,还是 Windows 中的结构化异常处理)会掩盖真正的错误。因此不要这样做。
  在使用 /analyze 进行编译时,C 或 C++ 中捕获所有异常(包括缓冲区溢出等访问保护错误)的结构化异常处理程序将产生一个 C6320 警告。这种设计使攻击者能够反复发起对动画光标错误 MS07-017 的攻击。如果您找出了异常处理错误,就给自己再加一分。
  错误 #4
      void func(const char *s) {
  if (!s) return;
  char t[3];
  memcpy(t,s,3);
  t[3] = 0;
  ...
  }

  答案:几年前当 Windows Vista 还处在开发过程中的时候,我们曾在其中发现了一个类似错误。但这是安全性错误吗?很明显,它是编码错误,因为代码将写入第四个数组元素,但数组只有三个元素长。请记住,数组是从零开始,而不是一。我坚持认为这不是安全性错误,因为攻击者没有获取任何控制权限。
  但是,如果该错误看上去像下面的示例那样,攻击者控制了 i,那么就意味着攻击者可以在内存中的任意位置写入零。这时它就成了真正的安全性错误:
      void func(const char *s, int i) {
  if (!s) return;
  char t[3];
  memcpy(t,s,3);
  t = 0;
  ...
  }

  使用 /analyze 进行编译时,此代码将生成 C6201“超出有效索引范围”警告。如果您说“不是安全性错误”,就给自己加一分。
  错误 #5
      public class Barrel {
  // By default, a barrel contains one rhesus monkey.
  private static Monkey[] defaultMonkeys =
  new[] { new RhesusMonkey() };
  // backing store for property.
  private IEnumerable monkeys = null;
  public IEnumerable Monkeys {
  get {
  if (monkeys == null) {
  if (MonkeysReady())
  monkeys = PopulateMonkeys();
  else
  monkeys = defaultMonkeys;
  }
  return monkeys;
  }
  }
  }

  答案:这是一个难题。此类作者认为它们是安全高效的。其后备存储是保密的,属性为只读,属性类型为 IEnumerable,因此调用方无法执行任何操作,只能读取 Barrel 的状态。
  作者忘记了心怀叵测的调用方可能会尝试将属性的返回值转换为 Monkey[]。如果有两个 Barrel,每个都有默认的 Monkey 列表,那么拥有其中一个 Barrel 的恶意调用方则可以使用其他任何 Monkey(或 null)来替换静态默认列表中的 RhesusMonkey,从而实际改变另一个 Barrel 的状态。
  此处的解决方案是缓存 ReadOnlyCollection 或其他某个真正的只读存储,以保护底层数组免受调用方恶意或意外的转换。如果您抓住了这一点,就给自己加两分。
  错误 #6 (C#)
      protected void Page_Load(object sender, EventArgs e) {
  string lastLogin = Request["LastLogin"];
  if (String.IsNullOrEmpty(lastLogin)) {
  HttpCookie lastLoginCookie = new HttpCookie("LastLogin",
  DateTime.Now.ToShortDateString());
  lastLoginCookie.Expires = DateTime.Now.AddYears(1);
  Response.Cookies.Add(lastLoginCookie);
  }
  else {
  Response.Write("Welcome back! You last logged in on " + lastLogin);
  Response.Cookies["LastLogin"].Value =
  DateTime.Now.ToShortDateString();
  }
  }

  答案:这是一个非常简单的、跨站点的脚本编写漏洞,也是 Web 上最常见的漏洞。尽管代码似乎暗示 lastLogin 值始终来自 cookie,但实际上 HttpRequest.Item 属性更倾向于使用来自查询字符串的值,而不是来自 cookie 的值。
  换句话说,无论 lastLogin cookie 被设置为何值,只要攻击者将成对的名称/值 lastLogin= 添加到查询字符串中,应用程序就会为 lastLogin 变量的值选择恶意脚本输入。如果您的回答是 XSS,则给自己加一分。
  错误 #7 (C#)
      private decimal? lookupPrice(XmlDocument doc) {
  XmlNode node = doc.SelectSingleNode(
  @"//products/product[id/text()=" +
  Request["itemId"] + "]/price");
  if (node == null)
  return null;
  else
  return (Convert.ToDecimal(node.InnerText));
  }

  答案:如果您说是 XPath 注入,则给自己加一分。XPath 注入的工作原理与其同类但却更为著名(也可以说是臭名昭著)的 SQL 注入完全一样。此代码创建了一个将 XPath 代码和未经验证的保留用户输入合并在一起的查询,因此很容易受到注入攻击。任何应用程序如果它所处理的文本随后将被用于执行某种形式的操作,则它们都面临注入攻击的威胁。
  错误 #8 (C#)
      public class CustomSessionIDManager : System.Web.Session State.SessionIDManager
  {
  private static object lockObject = new object();
  public override string CreateSessionID(HttpContext context)
  {
  lock (lockObject)
  {
  Int32? lastSessionId = (int?)context.Application ["LastSessionId"];
  if (lastSessionId == null)
  lastSessionId = 1;
  else
  lastS

相关文章
最新文章
热点推荐