本文将介绍我是如何与r2c的另一位开发人员合作,成功识别出日志中的数据泄露漏洞,并修复了该漏洞,并在日后彻底消除该漏洞的复发整个过程只需几个小时即可完成。作为一名开发人员和工程经理,我一直着迷于寻找一种方法来快速解决整个组织的安全漏洞,而无需安全团队的全面参与。你为什么想做这个?好处有很多:可以快速解决组织中出现的安全漏洞。在实践中,这种方法可以大大加快安全防御的速度,使我们在发现漏洞后几分钟内就可以建立安全防护措施。如果是组织流程,安全漏洞将持续数天或数周。当开发人员可以轻松地自行修复安全漏洞时,安全团队就可以腾出时间来专注于整个组织的“大局”安全。我希望安全工程师思考如何选择框架、设置工具、帮助实施安全架构和构建深度防御,而不是像我在本文中描述的那样发现XSS漏洞。上面的过程我称之为“自助DevSec”。接下来介绍一下我们在日常开发工作过程中遇到的一个安全漏洞,我会讨论一下我们是如何发现这个漏洞的,以及如何在短短几个小时内修复整个安全holewithin,并使用Semgrep来防止它再次发生。Semgrep是一个使用熟悉的语法进行轻量级静态分析的开源工具。上个月,我正在与另一位r2c工程师ClaraMcCreery一起调试Flaskweb应用程序验证过程。刚刚像许多工程师面对令人困惑的调试问题一样,我们的第一步是将Web应用程序放入调试日志中。具体来说,我们想知道我们的数据库操作发生了什么,所以我们设置我们的ORM(在本例中,我们使用SQLAlchemy)以在INFO级别记录:logging.getLogger("sqlalchemy.engine.base.Engine").setLevel(logging.INFO)这将SQLAlchemy配置为记录所有SQL语句以及传递的参数,让我们看看我们看到的一些输出:INFO:werkzeug:127.0.0.1--[25/Sep/202011:50:01]"POST/api/auth/authenticateHTTP/1.1"200-INFO:sqlalchemy.engine.base.Engine:BEGIN(implicit)INFO:sqlalchemy.engine.base.Engine:SELECTtoken.idASToken_id,token.tokenASToken_token,令牌.nameAStoken_nameFROMtokenWHEREtoken.token=%(token_1)sLIMIT%(param_1)sINFO:sqlalchemy.engine.base.Engine:{'token_1':$2a$10$KVsyW1jjKn.pvkVi3w9Rn.1mwnZFd7F2SFveGDG8fl41G':'par_amabsolute不应记录令牌如果它被安全地散列。在此示例中,出于说明目的,实际令牌值已更改。从计划开始此时,我们已经确定了一个安全漏洞并希望修复它,同时保留检查日志的能力。具体步骤如下:缓解当前的安全漏洞;在紧急情况下找到永久解决方案。永久解决方案意味着对我们的系统进行深刻的改变。理想情况下,该解决方案在整个组织中是自动化和无缝的。添加一种机制来强制在组织范围内使用我们的解决方案。接下来,我将引导您完成每个步骤。值得注意的是,我们能够在几个小时内完成整个过程,而无需与安全团队接触。当前安全漏洞的缓解这里的缓解非常简单,因为我们已经知道漏洞的根本原因,可以快速恢复日志更改过程。然后我们可以对日志进行快速审核,以确保只有开发测试令牌被泄露。永久解决方案那么我们如何防止SQLAlchemy记录敏感数据呢?第一步是阅读文档。快速搜索“引擎日志中的sqlalchemy隐藏参数”可将我们链接到SQLAlchemy引擎文档。稍后详细阅读,通过这种方式我们发现了hide_parameters标志,它可以防止日志记录框架在日志或异常中发出任何参数。虽然这肯定会防止发现安全漏洞,但它对我们来说信息量太大,因为我们想知道诸如数据库ID之类的信息以进行调试。真正的解决方案我们接着查看了相关的SQLAlchemy源码,在sqlalchemy/engine/base.py中:sql_util._repr_params依次运行:通过研究trunc,我们发现它是通过截断参数的repr到最大数量字符来转换参数值,这意味着我们应该覆盖参数对象的repr方法以防止敏感日志记录。这个时候,像优秀的工程师一样,我们采用了懒惰的策略。由于我发现的GitHub漏洞,MikeBayer已经发布了一个很好的解决方案,所以我复制了一些。关键代码如下:这段代码是做什么的?您可以看到它用新的ObfuscatedString.Repr参数替换了我们原来的str参数。该字符串将在登录时或发出异常消息时替换为our******。由于该参数仍被绑定为原始字符串(通过impl=types.String),因此仍会从数据库中插入和选择正确的值。要使用这个新的字段类型,我们将令牌的字段类型设置如下:然后我们重新启用INFO日志记录并检查我们是否正确混淆了文本:INFO:werkzeug:127.0.0.1--[25/Sep/202013:48:55]"GET/api/agent/deployments/1/policiesHTTP/1.1"200-INFO:sqlalchemy.engine.base.Engine:BEGIN(隐式)INFO:sqlalchemy.engine.base.Engine:SELECTtoken.idASToken_id,token.tokenASToken_token,token.nameAStoken_nameFROMtokenWHEREtoken.token=%(token_1)sLIMIT%(param_1)sINFO:sqlalchemy.engine.base.Engine:{'token_1':********,'param_1':1}为了完整性,我们还在开发数据库控制台中验证了正确的值被存储和检索。执行应该说我们已经暂时修复了安全漏洞,以便可以重新调试原来的认证漏洞。而是要彻底修复整个洞口。我们该怎么做?这里有一些想法,我相信我们都遇到过:在安全审查期间阻止对SQLAlchemy模型的所有提交。对所有开发人员进行年度安全培训,包括记录敏感数据的漏洞。每周审计日志。向您的SAST供应商提交错误,并要求他们添加检查以捕获敏感记录的数据。如果要从这篇博文中得出一个核心结论,那就是:由于以下原因,这些都不是理想的解决方案:阻止提交会在开发过程中引入不必要的延迟,减慢开发速度,并分散安全团队的注意力。安全培训是安全计划的重要组成部分,是让开发人员意识到不断变化的安全威胁所必需的,但人类的记忆力很差,我们可能会忘记几个月甚至几天前听到的事情。定期审计(例如阻止提交)会给几乎肯定超载的安全团队带来沉重的工作量;您的SAST提供商当然会欢迎您的建议,但您将依赖于他们的软件发布周期,并且可能几个月都看不到可用的支票。此外,如果您的漏洞是特定于域的,那么实施广泛检查甚至没有意义。幸运的是,Semgrep为我们提供了一个简单的解决方案:在代码中定义一个不变量,并在每次CI运行时通过Semgrep扫描强制执行。在r2c中,我们使用GitHubActions在每个合并请求上运行Semgrep。我们使用由Semgrep.dev管理的管理策略、规则字段表和通知设置来定义Semgrep应该运行哪些检查。为了确保我们的代码不会再次中断,我访问了semgrep.dev/editor并编写了一个快速规则来检测可能不安全的日志SQLAlchemy字段。这是Semgrep的YAML定义语言中的规则定义:此规则的作用是什么?详细解释:id:我们给规则一个简洁的描述性ID,以便任何在编辑器或CI输出中看到它的开发人员都可以轻松地参考它。patterns:这由两部分组成:pattern:这个表达式告诉Semgrep如何在我们的代码库中查找任何具有String字段类型的SQLAlchemyORM字段定义(在这个例子中,我们的SQLAlchemy实例称为db),它还绑定字段名称为名为COLUMN的元变量。metavvariable-regex:此表达式告诉Semgrep仅当字段元变量包含单词片段(例如token、email、key或secret)时才报告匹配项。正则表达式包含了很多细节声明,以防止我们匹配不相关的单词,如键盘。消息:当Semgrep匹配我们的模式时,我们要确保我们解释检测到的漏洞是什么,为什么它是一个漏洞,以及如何修复它。此信息将帮助开发人员独立解决漏洞,而不会造成混淆或不必要的误解。严重性:您可以自定义域中任何漏洞的严重性。然后快速按下“部署到策略”按钮可确保所有Web应用程序都受到保护。通过我们的VSCode扩展将Semgrep集成到他们的编程工作流程中的开发人员也将开始在他们的IDE中看到效果。请注意,此解决方案是有意迭代的:我们可能会发现更多的字段名称被标识为敏感字段,或者还希望包括db.Text类型。幸运的是,这是一个快速修复,并根据需要重新部署。总结在本文中,我演示了作为开发人员或经理的您如何使用Semgrep等轻量级静态分析来帮助在您的代码中实施不变量。在r2c中,我们习惯性地使用Semgrep来防止自己重蹈覆辙:不小心让调试器处于提交状态?有一条规则可以防止这种情况发生。当我们发现导入某个库会减慢程序的初始化速度时,我们写了一条规则来确保它是延迟加载的。本文翻译自:https://r2c.dev/blog/2020/fixing-leaky-logs-how-to-find-a-bug-and-ensure-it-never-returns/
