Skip to content

REQBODY_ERROR "Extra content at the end of the document" after 3.0.15 update #3563

Open

Description

@ssigwart

Describe the bug

I updated from version 3.0.14 to 3.0.15 today. Unfortunately, I don't have a ton of helpful info, but I've noticed a few errors that I haven't seen before with REQBODY_ERROR tripping.

Logs and dumps

Here's an example nginx error log (with IP and domain changed).

body.xml:1: parser error : Extra content at the end of the document
2026/05/12 20:51:59 [error] 22751#22751: *430298 [client 123.123.123.123] ModSecurity: Access denied with code 400 (phase 2). Matched "Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' ) [file "/etc/nginx/modsec/modsecurity.conf"] [line "53"] [id "200002"] [rev ""] [msg "Failed to parse request body."] [data "XML parsing error: XML: Failed to parse document."] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "example.com"] [uri "/"] [unique_id "177863351988.105338"] [ref "v269,1"], client: 123.123.123.123, server: example.com, request: "GET / HTTP/1.1", host: "example.com"

To Reproduce

Sorry, I don't know how to reproduce this. I've seen this a handful of times in millions of requests.

Server (please complete the following information):

  • ModSecurity version (and connector): ModSecurity v3.0.15 with nginx-connector v1.0.4
  • WebServer: nginx 1.26
  • OS (and distro): Amazon Linux 2

Rule Set (please complete the following information):

  • Running any public or commercial rule set? CRS
  • What is the version number? 4.26.0

Activity

  1. added
    3.xRelated to ModSecurity version 3.x
    on May 13, 2026
  2. airween commented on May 13, 2026

    @airween
    Member

    Hi @ssigwart,

    thanks for reporting this.

    Based on the message in the attached log this can happened at two places:

    The mentioned blocks have changed since 3.0.14, but the last condition is the same (in both cases):

        if (m_data.well_formed != 1) {

    Sorry, I don't know how to reproduce this. I've seen this a handful of times in millions of requests.

    Don't you have any audit.log with body part content? Without that, I'm afraid we can't investigate this issue. If you have a tons of this, probably that would be enough to allow audit.log (with body parts) for a short time.

  3. ssigwart commented on May 13, 2026

    @ssigwart
    Author

    Hi @airween,

    I do have some audit logs, but I thought it was basically the same info as the error log. I posted it here anyway. We don't include the request or response headers or body for security reasons and PCI compliance. It can make debugging harder though.

    ---4AxOFwLw---A--
    [12/May/2026:20:51:59 -0400] 177863351988.105338 123.123.123.123 43936 192.168.100.100 443
    ---4AxOFwLw---D--
    
    ---4AxOFwLw---F--
    HTTP/1.1 400
    Server: nginx
    Date: Wed, 13 May 2026 00:51:59 GMT
    Content-Length: 552
    Content-Type: text/html
    Connection: close
    
    ---4AxOFwLw---H--
    ModSecurity: Access denied with code 400 (phase 2). Matched "Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' ) [file "/etc/nginx/modsec/modsecurity.conf"] [line "53"] [id "200002"] [rev ""] [msg "Failed to parse request body."] [data "XML parsing error: XML: Failed to parse document."] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "example.com"] [uri "/"] [unique_id "177863351988.105338"] [ref "v269,1"]
    
    ---4AxOFwLw---I--
    
    ---4AxOFwLw---J--
    
    ---4AxOFwLw---Z--
    

    Thanks for pointing out the updated code. I looked at some requests before and after upgrading. We did have some things triggering rule 200002 before and after, so I don't think it's an issue that the rule is triggering. That's probably appropriately flagging malicious requests. However, the difference is the body.xml:1: parser error : Extra content at the end of the document line in the nginx error logs. Notice that it doesn't output with the normal Nginx error log line prefix (e.g. 2026/05/12 20:51:59 [error] 22751#22751: *430298 [client 123.123.123.123] ). That is what brought this to my attention. We have code that parses timestamps from the logs, but these didn't have one.

    I wonder if this needs an update similar to b9dc9cc.

  4. airween commented on May 13, 2026

    @airween
    Member

    I do have some audit logs, but I thought it was basically the same info as the error log. I posted it here anyway. We don't include the request or response headers or body for security reasons and PCI compliance. It can make debugging harder though.

    I see and I agree.

    But - I don't know how frequent is this issue, for a short period would it be possible to add the body parts (C) to SecAuditLogParts? Indeed, you might remove/change sensitive data.

    That's probably appropriately flagging malicious requests.

    So probably this is a normal behavior?

    Notice that it doesn't output with the normal Nginx error log line prefix (... ). That is what brought this to my attention. We have code that parses timestamps from the logs, but these didn't have one.

    Ah, I see.

    I wonder if this needs an update similar to b9dc9cc.

    Thanks, I'll take a look at that.

  5. ssigwart commented on May 13, 2026

    @ssigwart
    Author

    I do have some audit logs, but I thought it was basically the same info as the error log. I posted it here anyway. We don't include the request or response headers or body for security reasons and PCI compliance. It can make debugging harder though.

    I see and I agree.

    But - I don't know how frequent is this issue, for a short period would it be possible to add the body parts (C) to SecAuditLogParts? Indeed, you might remove/change sensitive data.

    It's so infrequent. In millions of requests yesterday, there were probably no more than 1 - 2 dozen. I can't enable the logging because PCI compliance requires log retention and deletion protection. Even if that wasn't a concern, I'm pretty sure the sheer volume of ModSecurity blocks I deal with would fill up our disk.

    That's probably appropriately flagging malicious requests.

    So probably this is a normal behavior?

    I think so. It's just the error log entry without the date prefix that's problematic.

    Notice that it doesn't output with the normal Nginx error log line prefix (... ). That is what brought this to my attention. We have code that parses timestamps from the logs, but these didn't have one.

    Ah, I see.

    I wonder if this needs an update similar to b9dc9cc.

    Thanks, I'll take a look at that.

    Thank you.

  6. ssigwart commented on May 13, 2026

    @ssigwart
    Author

    @airween, I found a really easy way to duplicate it. I ran the following against my server and saw it in the logs. I tried to use the CRS sandbox so it was a server you'd be able to see, but the command below results in a 500 server error.

    curl -H "x-format-output: txt-matched-rules" https://sandbox.coreruleset.org/ -H "Content-Type: application/xml" -i -X POST --data '<xml></xml> Testing modsec'
  7. airween commented on May 13, 2026

    @airween
    Member

    @ssigwart,

    thanks, I was able to reproduce that.

    But I got only this in my error.log:

    2026/05/13 18:12:58 [info] 132588#132588: *1 ModSecurity: Warning. Matched "Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' ) [file "/etc/nginx/modsecurity.conf"] [line "75"] [id "200002"] [rev ""] [msg "Failed to parse request body."] [data "XML parsing error: XML: Failed to parse document."] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "localhost"] [uri "/"] [unique_id "177868877880.055869"] [ref "v157,1"], client: 127.0.0.1, server: _, request: "POST / HTTP/1.1", host: "localhost:8080"
    2026/05/13 18:12:58 [info] 132588#132588: *1 client 127.0.0.1 closed keepalive connection
    

    Nothing more.
    libmodsecurity3 is the current upstream (Github) version, connector is 1.0.4 (stable released).

  8. ssigwart commented on May 14, 2026

    @ssigwart
    Author

    It took me a while, but I was able to reproduce this in docker. Here's what I did.

    docker run -ti --rm -p 8080:80 --platform=linux/arm64 amazonlinux:2023
    dnf install -y nginx gcc-c++ flex bison yajl-devel curl-devel libxml2-devel pcre2-devel git libtool wget vim
    cd /
    git clone --depth 1 -b v3/master https://github.com/SpiderLabs/ModSecurity
    cd ModSecurity/
    git submodule init && git submodule update --init --recursive
    ./build.sh && ./configure && make && make install
    
    cd /
    git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git
    nginxVersion=$(nginx -v 2>&1 | egrep -o [0-9.]+) # Currently 1.28.3
    wget https://github.com/nginx/nginx/releases/download/release-${nginxVersion}/nginx-${nginxVersion}.tar.gz
    tar xzvf nginx-${nginxVersion}.tar.gz
    cd nginx-$nginxVersion
    ./configure --with-compat --add-dynamic-module=/ModSecurity-nginx
    make modules
    cp objs/ngx_http_modsecurity_module.so /usr/share/nginx/modules/ngx_http_modsecurity_module.so
    
    
    cat <<'EOF' > /etc/nginx/nginx.conf
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log notice;
    pid /run/nginx.pid;
    
    load_module modules/ngx_http_modsecurity_module.so;
    
    include /usr/share/nginx/modules/*.conf;
    
    events {
        worker_connections 1024;
    }
    
    http {
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        access_log  /var/log/nginx/access.log  main;
    
        sendfile            on;
        tcp_nopush          on;
        keepalive_timeout   65;
        types_hash_max_size 4096;
    
        include             /etc/nginx/mime.types;
        default_type        application/octet-stream;
    
        # Load modular configuration files from the /etc/nginx/conf.d directory.
        # See http://nginx.org/en/docs/ngx_core_module.html#include
        # for more information.
        include /etc/nginx/conf.d/*.conf;
    
        server {
            listen       80;
            listen       [::]:80;
            server_name  _;
            root         /usr/share/nginx/html;
    
            # Load configuration files for the default server block.
            include /etc/nginx/default.d/*.conf;
    
            error_page 404 /404.html;
            location / {
              proxy_method POST;
              proxy_pass http://localhost;
              modsecurity on;
              modsecurity_rules_file /etc/crs4/modsecurity.conf;
              modsecurity_rules_file /etc/crs4/crs-setup.conf;
              modsecurity_rules_file /etc/crs4/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf;
              modsecurity_rules_file /etc/crs4/rules/REQUEST-901-INITIALIZATION.conf;
            }
        }
    }
    EOF
    
    wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.26.0.tar.gz
    mkdir /etc/crs4
    tar -xzvf v4.26.0.tar.gz --strip-components 1 -C /etc/crs4
    
    cd /etc/crs4/
    mv crs-setup.conf.example crs-setup.conf
    wget https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-nginx/refs/heads/master/.github/nginx/modsecurity.conf
    cp /ModSecurity/unicode.mapping /etc/crs4/
    cd rules/
    mv REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
    nginx
    tail -f /var/log/nginx/error.log

    Then, from outside of docker, I ran this:

    curl http://localhost:8080/ -H "Content-Type: application/xml" -i -X POST --data '<xml></xml> Testing modsec'
  9. airween commented on May 14, 2026

    @airween
    Member

    Thanks for the detailed explanation.

    tail -f /var/log/nginx/error.log
    

    What did you exactly get in the error log then?

  10. ssigwart commented on May 14, 2026

    @ssigwart
    Author

    Sorry, I closed the window already, but it included body.xml:1: parser error : Extra content at the end of the document like in the initial issue comment. If you want me to run it again, let me know.

  11. zffocussss commented on Sep 11, 2026

    @zffocussss

    ModSecurity 3.0.16 is also producing the similar errors.

    body.xml:1: parser error : Extra content at the end of the document

    body.xml:1: parser error : Document is empty

  12. airween commented on Sep 11, 2026

    @airween
    Member

    ModSecurity 3.0.16 is also producing the similar errors.

    body.xml:1: parser error : Extra content at the end of the document

    body.xml:1: parser error : Document is empty

    Hi @zffocussss,

    could you help us to share a minimal example with we can reproduce the issue?

  13. fzipi commented on Oct 12, 2026

    @fzipi
    Collaborator

    Revisiting this one. The XML processor itself did not change between 3.0.14 and 3.0.15 except for 624a0de ("Fix libxml2 related deprecated issues", October 2025), which replaced the deprecated libxml2 calls, and that is the candidate for a behaviour difference: the body.xml:1: parser error lines are libxml2's own error output, which previous builds did not surface the same way. Both reports show a GET with an XML Content-Type and an empty or non-XML body, where "Document is empty" / "Extra content" is what libxml2 says.

    To settle it we need one captured request (headers plus raw body) that passes on 3.0.14 and fails on 3.0.15. Pending feedback.

  14. ssigwart commented on Oct 13, 2026

    @ssigwart
    Author

    I followed the steps in #3563 (comment). The output is this:

    2026/10/13 01:35:26 [notice] 20333#20333: start worker process 20346
    
    
    body.xml:1: parser error : Extra content at the end of the document
    <xml></xml> Testing modsec
                ^
    2026/10/13 01:35:33 [error] 20334#20334: *1 [client 192.168.65.1] ModSecurity: Access denied with code 400 (phase 2). Matched "Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' ) [file "/etc/crs4/modsecurity.conf"] [line "75"] [id "200002"] [rev ""] [msg "Failed to parse request body."] [data "XML parsing error: XML: Failed to parse document."] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "localhost"] [uri "/"] [unique_id "179185533378.153190"] [ref "v121,1"], client: 192.168.65.1, server: _, request: "POST / HTTP/1.1", host: "localhost:8080"
    

    I then repeated it, but changed these lines (pcre instead of pcre2 and 3.0.14 instead of v3/master):

    dnf install -y nginx gcc-c++ flex bison yajl-devel curl-devel libxml2-devel pcre-devel git libtool wget vim
    git clone --depth 1 -b v3.0.14 https://github.com/SpiderLabs/ModSecurity

    The output there is:

    2026/10/13 01:49:30 [notice] 20846#20846: start worker process 20859
    
    
    2026/10/13 01:49:43 [error] 20847#20847: *1 [client 192.168.65.1] ModSecurity: Access denied with code 400 (phase 2). Matched "Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' ) [file "/etc/crs4/modsecurity.conf"] [line "75"] [id "200002"] [rev ""] [msg "Failed to parse request body."] [data "XML parsing error: XML: Failed parsing document."] [severity "2"] [ver ""] [maturity "0"] [accuracy "0"] [hostname "localhost"] [uri "/"] [unique_id "179185618383.595261"] [ref "v121,1"], client: 192.168.65.1, server: _, request: "POST / HTTP/1.1", host: "localhost:8081"
    
  15. fzipi commented on Oct 13, 2026

    @fzipi
    Collaborator

    Thanks @ssigwart, that comparison settled it. Root cause: 3.0.15 replaced the old null xmlSetGenericErrorFunc() handler with a direct ctxt->options |= XML_PARSE_NOERROR | XML_PARSE_NOWARNING. libxml2 only reads flags stored that way from 2.13 on; 2.9 through 2.12 (Amazon Linux 2023 ships 2.10) apply them inside xmlCtxtUseOptions(), so on those versions the flags were a no-op and libxml2 kept printing to stderr. That is also why it could not be reproduced on a newer distro.

    Fix in #3677: call xmlCtxtUseOptions() instead. Verified against libxml2 2.9.13 and 2.15.4.

  16. fzipi commented on Oct 13, 2026

    @fzipi
    Collaborator

    Follow-up for keeping the libxml2 message (instead of discarding it once #3677 silences stderr): #3678.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.xRelated to ModSecurity version 3.xpr available

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions