Discussion:
[mod-security-users] Blocking File uploads by contents
Justin Brown
2008-11-12 18:54:32 UTC
Permalink
Hi,
I'm trying to block certain file uploads via PHP using mod_security. I have
created a Bash script that scans file contents and returns 0 and 1 according
to the requirements for inspectFile. The script works fine when I execute it
from the command line, but I can't get mod_security to even run it. First
here is the rule I wrote:

##
SecRule FILES_TMPNAMES "@inspectFile /bin/modsecFileChecker.sh" \
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file upload
attempt',phase:2,t:none" ##

And here is what the debug log shows when I upload a file:
Recipe: Invoking rule 9fb98c0; [file /path/to/modsec.conf"] [line "6"].
...Rule 9fb98c0: SecRule "FILES_TMPNAMES" "@inspectFile
/usr/bin/modsecFileChecker.sh" "phase:2,deny,log,status:406,t:none"
...Rule returned 0.
...Time #2: 644

##
I know mod_security is not running the script at all because I added some
logging lines to the Bash script that write to a log file I created that is
separate from mod_security and Apache.

The script is owned by root and is set to 755.

Any help you could provide would be very helpful. Thank you.
Brian Rectanus
2008-11-12 19:44:05 UTC
Permalink
Post by Justin Brown
Hi,
I'm trying to block certain file uploads via PHP using mod_security. I
have created a Bash script that scans file contents and returns 0 and 1
according to the requirements for inspectFile. The script works fine
when I execute it from the command line, but I can't get mod_security to
##
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file upload
attempt',phase:2,t:none"
##
Recipe: Invoking rule 9fb98c0; [file /path/to/modsec.conf"] [line "6"].
/usr/bin/modsecFileChecker.sh" "phase:2,deny,log,status:406,t:none"
...Rule returned 0.
...Time #2: 644
##
I know mod_security is not running the script at all because I added
some logging lines to the Bash script that write to a log file I created
that is separate from mod_security and Apache.
The script is owned by root and is set to 755.
Any help you could provide would be very helpful. Thank you.
The docs for inspectFile are not very good. Actually, they are
non-existant, heh. The script *must* print to stdout. If the first
character output is '1' then the action is taken, otherwise (typically
outputting '0') the rule did not match and no action is taken. Just
exiting with a return code of 0 or 1 is not sufficient.

Additionally, your rule will only work if the body of the request has a
Content-Type of "multipart/form-data" and one of the parts has a
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a collection of
all of these filenames and if the collection is empty, then the rule is
not even processed, which seems to be your case.


Check the debug log (level 9) for something like this:

[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type" "text/x-diff"
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
[4] Multipart: Created temporary file:
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Changing file mode to 0640:
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Added file part 19df328 to the list: name "uploadFile"
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name "uploadFile" (offset
1007, length 11)


Quick test HTML page:

<html>
<head>
<title>Upload File Test</title>
</head>
<body>
<form name="Form1" method="post" action="/path/to/script" id="Form1"
enctype="multipart/form-data">
<input name="uploadFile" type="file" id="uploadFile" /><BR>
<input type="submit" name="uploadFile" value="Upload File"
id="uploadFile" />
</form>
</body>
</html>


-B
--
Brian Rectanus
Breach Security
Justin Brown
2008-11-12 22:48:42 UTC
Permalink
Thanks for your response, please see my comments below:


The docs for inspectFile are not very good. Actually, they are
Post by Brian Rectanus
non-existant, heh. The script *must* print to stdout. If the first
character output is '1' then the action is taken, otherwise (typically
outputting '0') the rule did not match and no action is taken. Just
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to exiting with
those values.

Additionally, your rule will only work if the body of the request has a
Post by Brian Rectanus
Content-Type of "multipart/form-data" and one of the parts has a
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a collection of
all of these filenames and if the collection is empty, then the rule is
not even processed, which seems to be your case.
I tried both my own form and the one you provided below. Neither of them
caused any Multipart: entries to appear in the log. Have I missed some sort
of configuration value I need to set in order for mod_security to handle
file uploads? Here is my full configuration file that Apache is including:

####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
# "Add the rules that will do exactly the same as the directives"
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1$" nolog,allow
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
SecRule FILES_TMPNAMES "@inspectFile /usr/bin/modsecFileChecker.sh" \
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file upload
attempt',phase:2,t:none"
</IfModule>

####

And here is the full level 9 output of my test.
####
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Initialising
transaction (txid ***@BGIAABctMfIAAAAD).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Transaction
context created (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Starting phase
REQUEST_HEADERS.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] This phase
consists of 0 rule(s).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] PdfProtect: Not
enabled here.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Second phase
starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Input filter:
Request body access not enabled.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Time #1: 465
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Starting phase
REQUEST_BODY.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] This phase
consists of 2 rule(s).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Recipe: Invoking
rule 918fe48; [file "/path/to/modsec.conf"] [line "16"].
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][5] Rule 918fe48:
SecRule "REMOTE_ADDR" "@rx ^127.0.0.1$" "phase:2,status:406,nolog,allow"
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Transformation
completed in 2 usec.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Executing
operator "rx" with param "^127.0.0.1$" against REMOTE_ADDR.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] Target value: "
123.123.123.123"
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Operator
completed in 2 usec.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Rule returned 0.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] No match, not
chained -> mode NEXT_RULE.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Recipe: Invoking
rule 9190638; [file "/path/to/modsec.conf"] [line "6"] [id "50"] [rev "1"].
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][5] Rule 9190638:
SecRule "FILES_TMPNAMES" "@inspectFile /usr/bin/modsecFileChecker.sh"
"phase:2,deny,log,status:406,auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP
file upload attempt',t:none"
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Rule returned 0.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] No match, not
chained -> mode NEXT_RULE.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Time #2: 672
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Hook
insert_filter: Adding PDF XSS protection output filter (r942d5d8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Hook
insert_filter: Adding output filter (r 942d5d8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] Output filter:
Receiving output (f 943f4d8, r 942d5d8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Starting phase
RESPONSE_HEADERS.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] This phase
consists of 0 rule(s).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Output filter:
Response body buffering is not enabled.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] Content
Injection: Not enabled.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Output filter:
Completed receiving response body (non-buffering).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Starting phase
RESPONSE_BODY.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] This phase
consists of 0 rule(s).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Output filter:
Output forwarding complete.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] Output filter:
Sending input brigade directly.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Initialising
logging.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4] Starting phase
LOGGING.
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][9] This phase
consists of 0 rule(s).
#####
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type" "text/x-diff"
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Added file part 19df328 to the list: name "uploadFile"
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name "uploadFile" (offset
1007, length 11)
As you can see, none of the Multipart entries you mentioned are appearing in
my log. Any ideas?
Brian Rectanus
2008-11-12 23:05:46 UTC
Permalink
See below...
Post by Brian Rectanus
The docs for inspectFile are not very good. Actually, they are
non-existant, heh. The script *must* print to stdout. If the first
character output is '1' then the action is taken, otherwise (typically
outputting '0') the rule did not match and no action is taken. Just
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to exiting with
those values.
Additionally, your rule will only work if the body of the request has a
Content-Type of "multipart/form-data" and one of the parts has a
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a collection of
all of these filenames and if the collection is empty, then the rule is
not even processed, which seems to be your case.
I tried both my own form and the one you provided below. Neither of them
caused any Multipart: entries to appear in the log. Have I missed some
sort of configuration value I need to set in order for mod_security to
handle file uploads? Here is my full configuration file that Apache is
####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
# "Add the rules that will do exactly the same as the directives"
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1 <http://127.0.0.1>$" nolog,allow
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
I should have mentioned turning it on ;)

# Allow access to the request body
SecRequestBodyAccess On

### May want to look at these as well:
# SecRequestBodyLimit <limit>
# SecRequestBodyNoFilesLimit <limit>
# SecRequestBodyInMemoryLimit <limit>

# Allow access to the response body:
# SecResponseBodyAccess On
### NOTE There are similar limits for response
Post by Brian Rectanus
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file upload
attempt',phase:2,t:none"
</IfModule>
####
And here is the full level 9 output of my test.
####
<snip>
Post by Brian Rectanus
Second phase starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4
<http://domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4>]
Input filter: Request body access not enabled.
Input filter: Request body access not enabled.

This was the key.

<snip>
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type" "text/x-diff"
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Added file part 19df328 to the list: name "uploadFile"
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name "uploadFile" (offset
1007, length 11)
As you can see, none of the Multipart entries you mentioned are
appearing in my log. Any ideas?
You (actually everybody) should read the "Processing Phases" section in
the docs:

http://www.modsecurity.org/documentation/modsecurity-apache/2.5.7/html-multipage/processing-phases.html

This should help alleviate some confusion (especially migrating from
1.9.x). I made a note to update the docs to mention the directives that
control the body phases (access, limits, etc).

https://www.modsecurity.org/tracker/browse/MODSEC-33

-B
--
Brian Rectanus
Breach Security
Justin Brown
2008-11-12 23:38:07 UTC
Permalink
Thanks! That did the trick; once I added "SecRequestBodyAccess On" to my
config file it started running the rule. Now the problem is with my script.
:( I get this in the audit_log:

####
--44a8c35a-H--
Message: Exec: Execution failed while reading output:
/usr/bin/modsecFileChecker.sh (End of file found)
Message: Rule processing failed.
####

Is there any documentation regarding the location, permissions, etc. that
are required for the approver script? As you can see I have it in /usr/bin
and the permissions are 755 root:root.

I saw someone on the mailing list archives talking about proper permissions
and location for chroot, but I'm not familiar with that concept and don't
believe it is implemented on my system.

Here is the full source of my script (it's designed to find files with a PHP
opener tag (<?) in them.

####
#!/bin/bash

#Verify that we have a valid filepath argument
if test -e "$1"
then
#Check for PHP open tag
phpOpenCount=`grep -c "<?" $1`

#if one or more lines are found, return modSec denied code
if [ "$phpOpenCount" -gt "0" ]
then
echo 1
else
echo 0
fi
else
#If a valid file has not passed in, return modSec denied code
echo 1
fi

####

BTW thanks for putting those notes in to update the docs. I did skim the
processing phases section and I read through all the Configuration
directives but I didn't connect the dots that "SecRequestBodyAccess On"
enabled access to uploaded files as well. I guess that's obvious (they are
part of the POST after all) but since the note didn't explicitly say so I
figured there might be a seperate setting relating to file uploads. So much
for my knowledge of HTTP. :)


On Wed, Nov 12, 2008 at 3:05 PM, Brian Rectanus
Post by Brian Rectanus
See below...
Post by Brian Rectanus
The docs for inspectFile are not very good. Actually, they are
non-existant, heh. The script *must* print to stdout. If the first
character output is '1' then the action is taken, otherwise
(typically
Post by Brian Rectanus
outputting '0') the rule did not match and no action is taken. Just
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to exiting with
those values.
Additionally, your rule will only work if the body of the request has
a
Post by Brian Rectanus
Content-Type of "multipart/form-data" and one of the parts has a
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a collection
of
Post by Brian Rectanus
all of these filenames and if the collection is empty, then the rule
is
Post by Brian Rectanus
not even processed, which seems to be your case.
I tried both my own form and the one you provided below. Neither of them
caused any Multipart: entries to appear in the log. Have I missed some
sort of configuration value I need to set in order for mod_security to
handle file uploads? Here is my full configuration file that Apache is
####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
Post by Brian Rectanus
# "Add the rules that will do exactly the same as the directives"
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1 <http://127.0.0.1>$" nolog,allow
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
I should have mentioned turning it on ;)
# Allow access to the request body
SecRequestBodyAccess On
# SecRequestBodyLimit <limit>
# SecRequestBodyNoFilesLimit <limit>
# SecRequestBodyInMemoryLimit <limit>
# SecResponseBodyAccess On
### NOTE There are similar limits for response
Post by Brian Rectanus
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file upload
attempt',phase:2,t:none"
</IfModule>
####
And here is the full level 9 output of my test.
####
<snip>
Post by Brian Rectanus
Second phase starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
<http://domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
]
Input filter: Request body access not enabled.
Input filter: Request body access not enabled.
This was the key.
<snip>
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type" "text/x-diff"
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Added file part 19df328 to the list: name "uploadFile"
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name "uploadFile"
(offset
Post by Brian Rectanus
1007, length 11)
As you can see, none of the Multipart entries you mentioned are
appearing in my log. Any ideas?
You (actually everybody) should read the "Processing Phases" section in
http://www.modsecurity.org/documentation/modsecurity-apache/2.5.7/html-multipage/processing-phases.html
This should help alleviate some confusion (especially migrating from
1.9.x). I made a note to update the docs to mention the directives that
control the body phases (access, limits, etc).
https://www.modsecurity.org/tracker/browse/MODSEC-33
-B
--
Brian Rectanus
Breach Security
Brian Rectanus
2008-11-13 00:14:17 UTC
Permalink
See below...
Post by Justin Brown
Thanks! That did the trick; once I added "SecRequestBodyAccess On" to my
config file it started running the rule. Now the problem is with my
####
--44a8c35a-H--
/usr/bin/modsecFileChecker.sh (End of file found)
Message: Rule processing failed.
####
Is there any documentation regarding the location, permissions, etc.
that are required for the approver script? As you can see I have it in
/usr/bin and the permissions are 755 root:root.
Not really. It needs to be read/execute for the user running Apache.

As root (if you can):

su - apache_user

Then try to run as that user.
Post by Justin Brown
I saw someone on the mailing list archives talking about proper
permissions and location for chroot, but I'm not familiar with that
concept and don't believe it is implemented on my system.
This involves setting up Apache to run in a virtual root (as in "/", not
the user) where you specify some directory to be considered the same as
the root directory "/". In this case, the script would need to reside
in /your/chroot/usr/bin as well as any libs and bash, etc. You (or your
sysadmin) would have had to set it up.

However, it looks like the script executed or you would have gotten:

Exec: Execution failed: /usr/bin/modsecFileChecker.sh (Some error here)

My guess is your script exited early because the file it is attempting
to read is not readable (see script comments below). You may need to do
this:

SecUploadFileMode 0644

Which will change the file mode (permissions) to octal 644 after it is
written.
Post by Justin Brown
Here is the full source of my script (it's designed to find files with a
PHP opener tag (<?) in them.
####
#!/bin/bash
#Verify that we have a valid filepath argument
if test -e "$1"
Probably want -r (file is readable) and not just -e (exists).
Post by Justin Brown
then
#Check for PHP open tag
phpOpenCount=`grep -c "<?" $1`
Probably do not have permissions on $1 and get an error for the grep
call (and thus no output). Check your exit codes ;)

phpOpenCount=`grep -c "<?" $1`
if [ "$?" -ne 0 ]; then
echo 1
fi
Post by Justin Brown
#if one or more lines are found, return modSec denied code
if [ "$phpOpenCount" -gt "0" ]
then
If $phpOpenCount is empty then you would get and error. Something like:

-bash: [: : integer expression expected
Post by Justin Brown
echo 1
else
echo 0
fi
else
fi

Remove the else and let fall through here.
Post by Justin Brown
#If a valid file has not passed in, return modSec denied code
echo 1
This is where you want to end up if you cannot read
Post by Justin Brown
fi
Move this up to replace else.
Post by Justin Brown
####
BTW thanks for putting those notes in to update the docs. I did skim the
processing phases section and I read through all the Configuration
directives but I didn't connect the dots that "SecRequestBodyAccess On"
enabled access to uploaded files as well. I guess that's obvious (they
are part of the POST after all) but since the note didn't explicitly say
so I figured there might be a seperate setting relating to file uploads.
So much for my knowledge of HTTP. :)
No problems. It is good to get comments. Add a comment to that ticket
if you find other issues.


-B
Post by Justin Brown
On Wed, Nov 12, 2008 at 3:05 PM, Brian Rectanus
See below...
Post by Brian Rectanus
The docs for inspectFile are not very good. Actually, they are
non-existant, heh. The script *must* print to stdout. If the
first
Post by Brian Rectanus
character output is '1' then the action is taken, otherwise
(typically
Post by Brian Rectanus
outputting '0') the rule did not match and no action is taken.
Just
Post by Brian Rectanus
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to
exiting with
Post by Brian Rectanus
those values.
Additionally, your rule will only work if the body of the
request has a
Post by Brian Rectanus
Content-Type of "multipart/form-data" and one of the parts has a
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a
collection of
Post by Brian Rectanus
all of these filenames and if the collection is empty, then
the rule is
Post by Brian Rectanus
not even processed, which seems to be your case.
I tried both my own form and the one you provided below. Neither
of them
Post by Brian Rectanus
caused any Multipart: entries to appear in the log. Have I missed some
sort of configuration value I need to set in order for mod_security to
handle file uploads? Here is my full configuration file that Apache is
####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
Post by Brian Rectanus
# "Add the rules that will do exactly the same as the directives"
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1 <http://127.0.0.1>
<http://127.0.0.1>$" nolog,allow
Post by Brian Rectanus
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
I should have mentioned turning it on ;)
# Allow access to the request body
SecRequestBodyAccess On
# SecRequestBodyLimit <limit>
# SecRequestBodyNoFilesLimit <limit>
# SecRequestBodyInMemoryLimit <limit>
# SecResponseBodyAccess On
### NOTE There are similar limits for response
Post by Brian Rectanus
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file upload
attempt',phase:2,t:none"
</IfModule>
####
And here is the full level 9 output of my test.
####
<snip>
Post by Brian Rectanus
Second phase starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4
<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
Post by Brian Rectanus
<http://domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4
<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>>]
Post by Brian Rectanus
Input filter: Request body access not enabled.
Input filter: Request body access not enabled.
This was the key.
<snip>
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type" "text/x-diff"
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Added file part 19df328 to the list: name
"uploadFile"
Post by Brian Rectanus
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition" "form-data;
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name
"uploadFile" (offset
Post by Brian Rectanus
1007, length 11)
As you can see, none of the Multipart entries you mentioned are
appearing in my log. Any ideas?
You (actually everybody) should read the "Processing Phases" section in
http://www.modsecurity.org/documentation/modsecurity-apache/2.5.7/html-multipage/processing-phases.html
This should help alleviate some confusion (especially migrating from
1.9.x). I made a note to update the docs to mention the directives that
control the body phases (access, limits, etc).
https://www.modsecurity.org/tracker/browse/MODSEC-33
-B
--
Brian Rectanus
Breach Security
--
Brian Rectanus
Breach Security
Justin Brown
2008-11-13 00:38:57 UTC
Permalink
Well we keep getting closer, there are no errors from the script now that I
set "SecUploadFileMode 0644", but it is still not blocking the file. Now the
debug log gives me:

####
...
Executing /usr/bin/modsecFileChecker.sh to inspect
/tmp/20081112-162049-***@BGIAAEzoMQoAAAAD-file-EKGOTw.
Exec: /usr/bin/modsecFileChecker.sh
Exec: First line from script output: "1"
Operator completed in 6253 usec.
Rule returned 0.
No match, not chained -> mode NEXT_RULE.
...
####

It looks to me like the script returned a 1 (denied) but then it says "Rule
returned 0". What am I missing?


On Wed, Nov 12, 2008 at 4:14 PM, Brian Rectanus
Post by Brian Rectanus
See below...
Post by Justin Brown
Thanks! That did the trick; once I added "SecRequestBodyAccess On" to my
config file it started running the rule. Now the problem is with my
####
--44a8c35a-H--
/usr/bin/modsecFileChecker.sh (End of file found)
Message: Rule processing failed.
####
Is there any documentation regarding the location, permissions, etc.
that are required for the approver script? As you can see I have it in
/usr/bin and the permissions are 755 root:root.
Not really. It needs to be read/execute for the user running Apache.
su - apache_user
Then try to run as that user.
Post by Justin Brown
I saw someone on the mailing list archives talking about proper
permissions and location for chroot, but I'm not familiar with that
concept and don't believe it is implemented on my system.
This involves setting up Apache to run in a virtual root (as in "/", not
the user) where you specify some directory to be considered the same as
the root directory "/". In this case, the script would need to reside
in /your/chroot/usr/bin as well as any libs and bash, etc. You (or your
sysadmin) would have had to set it up.
Exec: Execution failed: /usr/bin/modsecFileChecker.sh (Some error here)
My guess is your script exited early because the file it is attempting
to read is not readable (see script comments below). You may need to do
SecUploadFileMode 0644
Which will change the file mode (permissions) to octal 644 after it is
written.
Post by Justin Brown
Here is the full source of my script (it's designed to find files with a
PHP opener tag (<?) in them.
####
#!/bin/bash
#Verify that we have a valid filepath argument
if test -e "$1"
Probably want -r (file is readable) and not just -e (exists).
Post by Justin Brown
then
#Check for PHP open tag
phpOpenCount=`grep -c "<?" $1`
Probably do not have permissions on $1 and get an error for the grep
call (and thus no output). Check your exit codes ;)
phpOpenCount=`grep -c "<?" $1`
if [ "$?" -ne 0 ]; then
echo 1
fi
Post by Justin Brown
#if one or more lines are found, return modSec denied code
if [ "$phpOpenCount" -gt "0" ]
then
-bash: [: : integer expression expected
Post by Justin Brown
echo 1
else
echo 0
fi
else
fi
Remove the else and let fall through here.
Post by Justin Brown
#If a valid file has not passed in, return modSec denied code
echo 1
This is where you want to end up if you cannot read
Post by Justin Brown
fi
Move this up to replace else.
Post by Justin Brown
####
BTW thanks for putting those notes in to update the docs. I did skim the
processing phases section and I read through all the Configuration
directives but I didn't connect the dots that "SecRequestBodyAccess On"
enabled access to uploaded files as well. I guess that's obvious (they
are part of the POST after all) but since the note didn't explicitly say
so I figured there might be a seperate setting relating to file uploads.
So much for my knowledge of HTTP. :)
No problems. It is good to get comments. Add a comment to that ticket
if you find other issues.
-B
Post by Justin Brown
On Wed, Nov 12, 2008 at 3:05 PM, Brian Rectanus
See below...
Post by Brian Rectanus
The docs for inspectFile are not very good. Actually, they are
non-existant, heh. The script *must* print to stdout. If the
first
Post by Brian Rectanus
character output is '1' then the action is taken, otherwise
(typically
Post by Brian Rectanus
outputting '0') the rule did not match and no action is taken.
Just
Post by Brian Rectanus
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to
exiting with
Post by Brian Rectanus
those values.
Additionally, your rule will only work if the body of the
request has a
Post by Brian Rectanus
Content-Type of "multipart/form-data" and one of the parts has
a
Post by Justin Brown
Post by Brian Rectanus
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a
collection of
Post by Brian Rectanus
all of these filenames and if the collection is empty, then
the rule is
Post by Brian Rectanus
not even processed, which seems to be your case.
I tried both my own form and the one you provided below. Neither
of them
Post by Brian Rectanus
caused any Multipart: entries to appear in the log. Have I missed
some
Post by Justin Brown
Post by Brian Rectanus
sort of configuration value I need to set in order for mod_security
to
Post by Justin Brown
Post by Brian Rectanus
handle file uploads? Here is my full configuration file that Apache
is
Post by Justin Brown
Post by Brian Rectanus
####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
Post by Justin Brown
Post by Brian Rectanus
# "Add the rules that will do exactly the same as the directives"
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1 <http://127.0.0.1>
<http://127.0.0.1>$" nolog,allow
Post by Brian Rectanus
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
I should have mentioned turning it on ;)
# Allow access to the request body
SecRequestBodyAccess On
# SecRequestBodyLimit <limit>
# SecRequestBodyNoFilesLimit <limit>
# SecRequestBodyInMemoryLimit <limit>
# SecResponseBodyAccess On
### NOTE There are similar limits for response
\
Post by Justin Brown
Post by Brian Rectanus
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP file
upload
Post by Justin Brown
Post by Brian Rectanus
attempt',phase:2,t:none"
</IfModule>
####
And here is the full level 9 output of my test.
####
<snip>
Post by Brian Rectanus
Second phase starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
<
http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4
Post by Justin Brown
Post by Brian Rectanus
<http://domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
<
http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4
Post by Justin Brown
Post by Brian Rectanus
]
Input filter: Request body access not enabled.
Input filter: Request body access not enabled.
This was the key.
<snip>
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition"
"form-data;
Post by Justin Brown
Post by Brian Rectanus
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type" "text/x-diff"
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
[9] Multipart: Added file part 19df328 to the list: name
"uploadFile"
Post by Brian Rectanus
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition"
"form-data;
Post by Justin Brown
Post by Brian Rectanus
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name
"uploadFile" (offset
Post by Brian Rectanus
1007, length 11)
As you can see, none of the Multipart entries you mentioned are
appearing in my log. Any ideas?
You (actually everybody) should read the "Processing Phases" section
in
http://www.modsecurity.org/documentation/modsecurity-apache/2.5.7/html-multipage/processing-phases.html
Post by Justin Brown
This should help alleviate some confusion (especially migrating from
1.9.x). I made a note to update the docs to mention the directives
that
Post by Justin Brown
control the body phases (access, limits, etc).
https://www.modsecurity.org/tracker/browse/MODSEC-33
-B
--
Brian Rectanus
Breach Security
--
Brian Rectanus
Breach Security
Brian Rectanus
2008-11-13 00:47:13 UTC
Permalink
Because we both got that backwards ;) After looking at the source:

'1' is okay, otherwise it is rejected.

So, just reverse your 0 and 1 in the exit calls.

Sorry. I'll blame Ivan for that inconsistency :)

-B
Post by Justin Brown
Well we keep getting closer, there are no errors from the script now
that I set "SecUploadFileMode 0644", but it is still not blocking the
####
...
Executing /usr/bin/modsecFileChecker.sh to inspect
Exec: /usr/bin/modsecFileChecker.sh
Exec: First line from script output: "1"
Operator completed in 6253 usec.
Rule returned 0.
No match, not chained -> mode NEXT_RULE.
...
####
It looks to me like the script returned a 1 (denied) but then it says
"Rule returned 0". What am I missing?
On Wed, Nov 12, 2008 at 4:14 PM, Brian Rectanus
See below...
Post by Justin Brown
Thanks! That did the trick; once I added "SecRequestBodyAccess On"
to my
Post by Justin Brown
config file it started running the rule. Now the problem is with my
####
--44a8c35a-H--
/usr/bin/modsecFileChecker.sh (End of file found)
Message: Rule processing failed.
####
Is there any documentation regarding the location, permissions, etc.
that are required for the approver script? As you can see I have it in
/usr/bin and the permissions are 755 root:root.
Not really. It needs to be read/execute for the user running Apache.
su - apache_user
Then try to run as that user.
Post by Justin Brown
I saw someone on the mailing list archives talking about proper
permissions and location for chroot, but I'm not familiar with that
concept and don't believe it is implemented on my system.
This involves setting up Apache to run in a virtual root (as in "/", not
the user) where you specify some directory to be considered the same as
the root directory "/". In this case, the script would need to reside
in /your/chroot/usr/bin as well as any libs and bash, etc. You (or your
sysadmin) would have had to set it up.
Exec: Execution failed: /usr/bin/modsecFileChecker.sh (Some error here)
My guess is your script exited early because the file it is attempting
to read is not readable (see script comments below). You may need to do
SecUploadFileMode 0644
Which will change the file mode (permissions) to octal 644 after it is
written.
Post by Justin Brown
Here is the full source of my script (it's designed to find files
with a
Post by Justin Brown
PHP opener tag (<?) in them.
####
#!/bin/bash
#Verify that we have a valid filepath argument
if test -e "$1"
Probably want -r (file is readable) and not just -e (exists).
Post by Justin Brown
then
#Check for PHP open tag
phpOpenCount=`grep -c "<?" $1`
Probably do not have permissions on $1 and get an error for the grep
call (and thus no output). Check your exit codes ;)
phpOpenCount=`grep -c "<?" $1`
if [ "$?" -ne 0 ]; then
echo 1
fi
Post by Justin Brown
#if one or more lines are found, return modSec denied code
if [ "$phpOpenCount" -gt "0" ]
then
-bash: [: : integer expression expected
Post by Justin Brown
echo 1
else
echo 0
fi
else
fi
Remove the else and let fall through here.
Post by Justin Brown
#If a valid file has not passed in, return modSec denied code
echo 1
This is where you want to end up if you cannot read
Post by Justin Brown
fi
Move this up to replace else.
Post by Justin Brown
####
BTW thanks for putting those notes in to update the docs. I did
skim the
Post by Justin Brown
processing phases section and I read through all the Configuration
directives but I didn't connect the dots that
"SecRequestBodyAccess On"
Post by Justin Brown
enabled access to uploaded files as well. I guess that's obvious (they
are part of the POST after all) but since the note didn't
explicitly say
Post by Justin Brown
so I figured there might be a seperate setting relating to file
uploads.
Post by Justin Brown
So much for my knowledge of HTTP. :)
No problems. It is good to get comments. Add a comment to that ticket
if you find other issues.
-B
Post by Justin Brown
On Wed, Nov 12, 2008 at 3:05 PM, Brian Rectanus
See below...
Post by Brian Rectanus
The docs for inspectFile are not very good. Actually,
they are
Post by Justin Brown
Post by Brian Rectanus
non-existant, heh. The script *must* print to stdout.
If the
Post by Justin Brown
first
Post by Brian Rectanus
character output is '1' then the action is taken, otherwise
(typically
Post by Brian Rectanus
outputting '0') the rule did not match and no action is
taken.
Post by Justin Brown
Just
Post by Brian Rectanus
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to
exiting with
Post by Brian Rectanus
those values.
Additionally, your rule will only work if the body of the
request has a
Post by Brian Rectanus
Content-Type of "multipart/form-data" and one of the
parts has a
Post by Justin Brown
Post by Brian Rectanus
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is a
collection of
Post by Brian Rectanus
all of these filenames and if the collection is empty, then
the rule is
Post by Brian Rectanus
not even processed, which seems to be your case.
I tried both my own form and the one you provided below. Neither
of them
Post by Brian Rectanus
caused any Multipart: entries to appear in the log. Have I
missed some
Post by Justin Brown
Post by Brian Rectanus
sort of configuration value I need to set in order for
mod_security to
Post by Justin Brown
Post by Brian Rectanus
handle file uploads? Here is my full configuration file that
Apache is
Post by Justin Brown
Post by Brian Rectanus
####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
Post by Justin Brown
Post by Brian Rectanus
# "Add the rules that will do exactly the same as the
directives"
Post by Justin Brown
Post by Brian Rectanus
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1 <http://127.0.0.1>
<http://127.0.0.1>
Post by Justin Brown
<http://127.0.0.1>$" nolog,allow
Post by Brian Rectanus
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
I should have mentioned turning it on ;)
# Allow access to the request body
SecRequestBodyAccess On
# SecRequestBodyLimit <limit>
# SecRequestBodyNoFilesLimit <limit>
# SecRequestBodyInMemoryLimit <limit>
# SecResponseBodyAccess On
### NOTE There are similar limits for response
/usr/bin/modsecFileChecker.sh" \
Post by Justin Brown
Post by Brian Rectanus
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP
file upload
Post by Justin Brown
Post by Brian Rectanus
attempt',phase:2,t:none"
</IfModule>
####
And here is the full level 9 output of my test.
####
<snip>
Post by Brian Rectanus
Second phase starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4
<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
<http://domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4
<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>
<http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4>>]
Post by Justin Brown
Post by Brian Rectanus
Input filter: Request body access not enabled.
Input filter: Request body access not enabled.
This was the key.
<snip>
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition"
"form-data;
Post by Justin Brown
Post by Brian Rectanus
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type"
"text/x-diff"
Post by Justin Brown
Post by Brian Rectanus
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
Post by Justin Brown
Post by Brian Rectanus
[9] Multipart: Added file part 19df328 to the list: name
"uploadFile"
Post by Brian Rectanus
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition"
"form-data;
Post by Justin Brown
Post by Brian Rectanus
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name
"uploadFile" (offset
Post by Brian Rectanus
1007, length 11)
As you can see, none of the Multipart entries you mentioned are
appearing in my log. Any ideas?
You (actually everybody) should read the "Processing Phases"
section in
http://www.modsecurity.org/documentation/modsecurity-apache/2.5.7/html-multipage/processing-phases.html
Post by Justin Brown
This should help alleviate some confusion (especially
migrating from
Post by Justin Brown
1.9.x). I made a note to update the docs to mention the
directives that
Post by Justin Brown
control the body phases (access, limits, etc).
https://www.modsecurity.org/tracker/browse/MODSEC-33
-B
--
Brian Rectanus
Breach Security
--
Brian Rectanus
Breach Security
--
Brian Rectanus
Breach Security
Justin Brown
2008-11-13 02:45:20 UTC
Permalink
Success!! The rule and script is working as desired. The only thing I had to
add was an I switch to the grep command so it wouldn't try to search binary
files (like jpgs). the new command is `grep -Ic "<?" $1`

Thanks so much for your help, Brian. When I have time I'll write up a step
by step in case anyone else wants to reproduce my results. I think this will
go a long way to stop people from uploading malicious PHP files through
vulnerable applications on our server (we have a large number of hosting
clients and there's just no way to keep all the PHP apps up to date without
going super draconian on our clients). Thanks again.

On Wed, Nov 12, 2008 at 4:47 PM, Brian Rectanus
Post by Brian Rectanus
'1' is okay, otherwise it is rejected.
So, just reverse your 0 and 1 in the exit calls.
Sorry. I'll blame Ivan for that inconsistency :)
-B
Post by Justin Brown
Well we keep getting closer, there are no errors from the script now
that I set "SecUploadFileMode 0644", but it is still not blocking the
####
...
Executing /usr/bin/modsecFileChecker.sh to inspect
Exec: /usr/bin/modsecFileChecker.sh
Exec: First line from script output: "1"
Operator completed in 6253 usec.
Rule returned 0.
No match, not chained -> mode NEXT_RULE.
...
####
It looks to me like the script returned a 1 (denied) but then it says
"Rule returned 0". What am I missing?
On Wed, Nov 12, 2008 at 4:14 PM, Brian Rectanus
See below...
Post by Justin Brown
Thanks! That did the trick; once I added "SecRequestBodyAccess On"
to my
Post by Justin Brown
config file it started running the rule. Now the problem is with my
####
--44a8c35a-H--
/usr/bin/modsecFileChecker.sh (End of file found)
Message: Rule processing failed.
####
Is there any documentation regarding the location, permissions,
etc.
Post by Justin Brown
Post by Justin Brown
that are required for the approver script? As you can see I have it
in
Post by Justin Brown
Post by Justin Brown
/usr/bin and the permissions are 755 root:root.
Not really. It needs to be read/execute for the user running Apache.
su - apache_user
Then try to run as that user.
Post by Justin Brown
I saw someone on the mailing list archives talking about proper
permissions and location for chroot, but I'm not familiar with that
concept and don't believe it is implemented on my system.
This involves setting up Apache to run in a virtual root (as in "/",
not
Post by Justin Brown
the user) where you specify some directory to be considered the same
as
Post by Justin Brown
the root directory "/". In this case, the script would need to
reside
Post by Justin Brown
in /your/chroot/usr/bin as well as any libs and bash, etc. You (or
your
Post by Justin Brown
sysadmin) would have had to set it up.
Exec: Execution failed: /usr/bin/modsecFileChecker.sh (Some error
here)
Post by Justin Brown
My guess is your script exited early because the file it is
attempting
Post by Justin Brown
to read is not readable (see script comments below). You may need to
do
Post by Justin Brown
SecUploadFileMode 0644
Which will change the file mode (permissions) to octal 644 after it
is
Post by Justin Brown
written.
Post by Justin Brown
Here is the full source of my script (it's designed to find files
with a
Post by Justin Brown
PHP opener tag (<?) in them.
####
#!/bin/bash
#Verify that we have a valid filepath argument
if test -e "$1"
Probably want -r (file is readable) and not just -e (exists).
Post by Justin Brown
then
#Check for PHP open tag
phpOpenCount=`grep -c "<?" $1`
Probably do not have permissions on $1 and get an error for the grep
call (and thus no output). Check your exit codes ;)
phpOpenCount=`grep -c "<?" $1`
if [ "$?" -ne 0 ]; then
echo 1
fi
Post by Justin Brown
#if one or more lines are found, return modSec denied code
if [ "$phpOpenCount" -gt "0" ]
then
If $phpOpenCount is empty then you would get and error. Something
-bash: [: : integer expression expected
Post by Justin Brown
echo 1
else
echo 0
fi
else
fi
Remove the else and let fall through here.
Post by Justin Brown
#If a valid file has not passed in, return modSec denied
code
Post by Justin Brown
Post by Justin Brown
echo 1
This is where you want to end up if you cannot read
Post by Justin Brown
fi
Move this up to replace else.
Post by Justin Brown
####
BTW thanks for putting those notes in to update the docs. I did
skim the
Post by Justin Brown
processing phases section and I read through all the Configuration
directives but I didn't connect the dots that
"SecRequestBodyAccess On"
Post by Justin Brown
enabled access to uploaded files as well. I guess that's obvious
(they
Post by Justin Brown
Post by Justin Brown
are part of the POST after all) but since the note didn't
explicitly say
Post by Justin Brown
so I figured there might be a seperate setting relating to file
uploads.
Post by Justin Brown
So much for my knowledge of HTTP. :)
No problems. It is good to get comments. Add a comment to that
ticket
Post by Justin Brown
if you find other issues.
-B
Post by Justin Brown
On Wed, Nov 12, 2008 at 3:05 PM, Brian Rectanus
See below...
Post by Brian Rectanus
The docs for inspectFile are not very good. Actually,
they are
Post by Justin Brown
Post by Brian Rectanus
non-existant, heh. The script *must* print to stdout.
If the
Post by Justin Brown
first
Post by Brian Rectanus
character output is '1' then the action is taken,
otherwise
Post by Justin Brown
Post by Justin Brown
(typically
Post by Brian Rectanus
outputting '0') the rule did not match and no action is
taken.
Post by Justin Brown
Just
Post by Brian Rectanus
exiting with a return code of 0 or 1 is not sufficient.
My Bash script is echoing a 0 or 1 to stdout in addition to
exiting with
Post by Brian Rectanus
those values.
Additionally, your rule will only work if the body of the
request has a
Post by Brian Rectanus
Content-Type of "multipart/form-data" and one of the
parts has a
Post by Justin Brown
Post by Brian Rectanus
Content-Disposition header with a filename= parameter (a
multipart/form-data file upload). The FILES_TMPNAMES is
a
Post by Justin Brown
Post by Justin Brown
collection of
Post by Brian Rectanus
all of these filenames and if the collection is empty,
then
Post by Justin Brown
Post by Justin Brown
the rule is
Post by Brian Rectanus
not even processed, which seems to be your case.
I tried both my own form and the one you provided below.
Neither
Post by Justin Brown
Post by Justin Brown
of them
Post by Brian Rectanus
caused any Multipart: entries to appear in the log. Have I
missed some
Post by Justin Brown
Post by Brian Rectanus
sort of configuration value I need to set in order for
mod_security to
Post by Justin Brown
Post by Brian Rectanus
handle file uploads? Here is my full configuration file that
Apache is
Post by Justin Brown
Post by Brian Rectanus
####
LoadFile /opt/xml2/lib/libxml2.so
LoadFile /opt/lua/lib/liblua.so
LoadModule security2_module modules/mod_security2.so
<IfModule mod_security2.c>
SecRuleEngine On
# See
http://www.modsecurity.org/documentation/ModSecurity-Migration-Matrix.pdf
Post by Justin Brown
Post by Justin Brown
Post by Brian Rectanus
# "Add the rules that will do exactly the same as the
directives"
Post by Justin Brown
Post by Brian Rectanus
# SecFilterCheckURLEncoding On
# SecFilterForceByteRange 0 255
SecAuditEngine RelevantOnly
SecAuditLog logs/modsec_audit.log
SecDebugLog logs/modsec_debug_log
SecDebugLogLevel 0
#SecDebugLogLevel 9
SecDefaultAction "phase:2,deny,log,status:406"
SecRule REMOTE_ADDR "^127.0.0.1 <http://127.0.0.1>
<http://127.0.0.1>
Post by Justin Brown
<http://127.0.0.1>$" nolog,allow
Post by Brian Rectanus
SecTmpDir /tmp
SecUploadDir /path/to/uploadtmp/
SecUploadKeepFiles On
I should have mentioned turning it on ;)
# Allow access to the request body
SecRequestBodyAccess On
# SecRequestBodyLimit <limit>
# SecRequestBodyNoFilesLimit <limit>
# SecRequestBodyInMemoryLimit <limit>
# SecResponseBodyAccess On
### NOTE There are similar limits for response
/usr/bin/modsecFileChecker.sh" \
Post by Justin Brown
Post by Brian Rectanus
"auditlog,id:50,rev:1,severity:CRITICAL,msg:'PHP
file upload
Post by Justin Brown
Post by Brian Rectanus
attempt',phase:2,t:none"
</IfModule>
####
And here is the full level 9 output of my test.
####
<snip>
Post by Brian Rectanus
Second phase starting (dcfg 8a313c8).
[domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4<http://domain.com/sid#919ce70%5D%5Brid#942d5d8%5D%5B/uptest/index.php%5D%5B4>
<
http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4
Post by Justin Brown
<
http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4
Post by Justin Brown
<http://domain.com/sid#919ce70][rid#942d5d8][/uptest/index.php][4<http://domain.com/sid#919ce70%5D%5Brid#942d5d8%5D%5B/uptest/index.php%5D%5B4>
<
http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4
Post by Justin Brown
<
http://domain.com/sid#919ce70%5D%5Brid%23942d5d8%5D%5B/uptest/index.php%5D%5B4
Post by Justin Brown
Post by Justin Brown
]
Post by Brian Rectanus
Input filter: Request body access not enabled.
Input filter: Request body access not enabled.
This was the key.
<snip>
Post by Brian Rectanus
[9] Multipart: Added part header "Content-Disposition"
"form-data;
Post by Justin Brown
Post by Brian Rectanus
name=\"uploadFile\"; filename=\"xml-fix.diff\""
[9] Multipart: Added part header "Content-Type"
"text/x-diff"
Post by Justin Brown
Post by Brian Rectanus
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Content-Disposition filename: xml-fix.diff
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
/apps/tmp/20081112-113554-SRswGn8AAQEAAG0CYu8AAADB-file-rBf70z
Post by Justin Brown
Post by Brian Rectanus
[9] Multipart: Added file part 19df328 to the list: name
"uploadFile"
Post by Brian Rectanus
file name "xml-fix.diff" (offset 165, length 727)
[9] Multipart: Added part header "Content-Disposition"
"form-data;
Post by Justin Brown
Post by Brian Rectanus
name=\"uploadFile\""
[9] Multipart: Content-Disposition name: uploadFile
[9] Multipart: Added data to variable: Upload File
[9] Multipart: Added part 19dfde0 to the list: name
"uploadFile" (offset
Post by Brian Rectanus
1007, length 11)
As you can see, none of the Multipart entries you mentioned
are
Post by Justin Brown
Post by Justin Brown
Post by Brian Rectanus
appearing in my log. Any ideas?
You (actually everybody) should read the "Processing Phases"
section in
http://www.modsecurity.org/documentation/modsecurity-apache/2.5.7/html-multipage/processing-phases.html
Post by Justin Brown
Post by Justin Brown
This should help alleviate some confusion (especially
migrating from
Post by Justin Brown
1.9.x). I made a note to update the docs to mention the
directives that
Post by Justin Brown
control the body phases (access, limits, etc).
https://www.modsecurity.org/tracker/browse/MODSEC-33
-B
--
Brian Rectanus
Breach Security
--
Brian Rectanus
Breach Security
--
Brian Rectanus
Breach Security
Ryan Barnett
2008-11-13 21:11:00 UTC
Permalink
From: Justin Brown [mailto:***@gmail.com]
Sent: Wednesday, November 12, 2008 9:45 PM
To: Brian Rectanus
Cc: mod-security-***@lists.sourceforge.net
Subject: Re: [mod-security-users] Fwd: Blocking File uploads by contents

Success!! The rule and script is working as desired. The only thing I had to add was an I switch to the grep command so it wouldn't try to search binary files (like jpgs). the new command is `grep -Ic "<?" $1`

Thanks so much for your help, Brian. When I have time I'll write up a step by step in case anyone else wants to reproduce my results. I think this will go a long way to stop people from uploading malicious PHP files through vulnerable applications on our server (we have a large number of hosting clients and there's just no way to keep all the PHP apps up to date without going super draconian on our clients). Thanks again.
[Ryan Barnett] I am glad that you got the @inspectFile operator to work appropriately for you, however it seems to me that in this particular case you may not need it. If you are using this operator to simply run a GREP for some PHP strings within the uploaded data then you should be able to use the following Core Rule to accomplish the same goal without having to fire off a script -
#
# PHP injection
#
SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES|REQUEST_HEADERS|XML:/* "@pm <? fgets move_uploaded_file $_session readfile ftp_put ftp_fget gzencode ftp_nb_put bzopen readdir $_post fopen gzread ftp_nb_fput ftp_nb_fget ftp_get $_get scandir fscanf readgzfile fread proc_open fgetc fgetss ftp_fput ftp_nb_get session_start fwrite gzwrite gzopen gzcompress" \
"phase:2,t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,pass,nolog,skip:1"
SecAction pass,nolog,skipAfter:959013
SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES "(?:(?:\b(?:f(?:tp_(?:nb_)?f?(?:ge|pu)t|get(?:s?s|c)|scanf|write|open|read)|gz(?:(?:encod|writ)e|compress|open|read)|s(?:ession_start|candir)|read(?:(?:gz)?file|dir)|move_uploaded_file|(?:proc_|bz)open)|\$_(?:(?:pos|ge)t|session))\b|<\?(?!xml))" \
"phase:2,capture,t:none,t:htmlEntityDecode,t:lowercase,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'PHP Injection Attack',id:'950013',tag:'WEB_ATTACK/PHP_INJECTION',tag:'WEB_ATTACK/HTTP_RESPONSSE_SPLITTING',logdata:'%{TX.0}',severity:'2'"
SecRule REQUEST_HEADERS|XML:/* "(?:(?:\b(?:f(?:tp_(?:nb_)?f?(?:ge|pu)t|get(?:s?s|c)|scanf|write|open|read)|gz(?:(?:encod|writ)e|compress|open|read)|s(?:ession_start|candir)|read(?:(?:gz)?file|dir)|move_uploaded_file|(?:proc_|bz)open)|\$_(?:(?:pos|ge)t|session))\b|<\?(?!xml))" \
"phase:2,capture,t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,ctl:auditLogParts=+E,deny,log,auditlog,status:501,msg:'PHP Injection Attack',id:'959013',tag:'WEB_ATTACK/PHP_INJECTION',tag:'WEB_ATTACK/HTTP_RESPONSSE_SPLITTING',logdata:'%{TX.0}',severity:'2'"
Loading...