Skip to content

Commit

Permalink
Merge pull request #10 from SasanLabs/develop
Browse files Browse the repository at this point in the history
Adding PHP and HTAccess files upload scan rule
  • Loading branch information
preetkaran20 authored Sep 17, 2021
2 parents 844c7ba + 1809fee commit b961173
Show file tree
Hide file tree
Showing 23 changed files with 536 additions and 22 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
All notable changes to this add-on will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.1.0] - 2021-09-16
- Scan rule for uploading:
- HTACCESS file
- PHP and its variants
- JPEG AND GIF images with PHP code snippet
- Skipping the scan rule initialization if add-on configuration is not present.

## [1.0.1] - 2021-08-19
- Minor change
- Scan rule will only execute if add-on configuration is specified.
Expand Down
3 changes: 1 addition & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ plugins {

repositories {
mavenCentral()
mavenLocal()
maven {
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
Expand All @@ -37,7 +36,7 @@ tasks.compileJava {

tasks.withType<JavaCompile>().configureEach { options.encoding = "utf-8"}

version = "1.0.1"
version = "1.1.0"
description = "Detect File upload requests and scan them to find related vulnerabilities"

zapAddOn {
Expand Down
20 changes: 14 additions & 6 deletions src/main/java/org/sasanlabs/fileupload/FileUploadScanRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,29 @@ public class FileUploadScanRule extends AbstractAppParamPlugin {

@Override
public void init() {
if (!this.isConfigured()) {
getParent()
.pluginSkipped(
this,
FileUploadI18n.getMessage(
"fileupload.configuration.not.present.skipping.scanrule"));
return;
}
switch (this.getAttackStrength()) {
case LOW:
maxRequestCount = 30;
maxRequestCount = 75;
break;
case MEDIUM:
maxRequestCount = 60;
maxRequestCount = 150;
break;
case HIGH:
maxRequestCount = 90;
maxRequestCount = 250;
break;
case INSANE:
maxRequestCount = 150;
maxRequestCount = 450;
break;
default:
maxRequestCount = 60;
maxRequestCount = 150;
break;
}
}
Expand Down Expand Up @@ -114,7 +122,7 @@ protected void scan(List<NameValuePair> nameValuePairs) {
}
}
}
if (isMultipart && isConfigured()) {
if (isMultipart) {
FileUploadAttackExecutor fileUploadAttackExecutor =
new FileUploadAttackExecutor(
this, nameValuePairs, originalFileName, originalContentType);
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/org/sasanlabs/fileupload/FileUploadUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
package org.sasanlabs.fileupload;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -210,4 +211,66 @@ static List<FileInformationProvider> getFileInformationProvidersDefaultJsp(
FileExtensionOperation.SUFFIX_ORIGINAL_EXTENSION)
.build());
}

/**
* Provides the list of FileInformationProviders for PHP by baseFileName and php extension
*
* @param baseFileName, base file name of uploaded php file.
* @param phpExtension, extension of the uploaded php file.
* @return list of FileInformationProvider for PHP
*/
static List<FileInformationProvider> getFileInformationProvidersByExtensionPHP(
String baseFileName, String phpExtension) {
return Arrays.asList(
new FileInformationProviderBuilder(baseFileName)
.withExtension(phpExtension)
.withFileExtensionOperation(FileExtensionOperation.ONLY_PROVIDED_EXTENSION)
.build(),
new FileInformationProviderBuilder(baseFileName)
.withExtension(phpExtension)
.withContentType("application/x-httpd-php")
.withFileExtensionOperation(FileExtensionOperation.ONLY_PROVIDED_EXTENSION)
.build(),
new FileInformationProviderBuilder(baseFileName)
.withExtension(phpExtension)
.withFileExtensionOperation(
FileExtensionOperation.PREFIX_ORIGINAL_EXTENSION)
.build(),
new FileInformationProviderBuilder(baseFileName)
.withExtension(phpExtension)
.withContentType("application/x-httpd-php")
.withFileExtensionOperation(
FileExtensionOperation.PREFIX_ORIGINAL_EXTENSION)
.build(),

/**
* If .htaccess has following configuration <code>
* AddHandler application/x-httpd-php .php</code> and if the file ends with the
* original extension but has .php in the name then also php will be executed For
* more information: https://www.acunetix.com/websitesecurity/upload-forms-threat/
*/
new FileInformationProviderBuilder(baseFileName)
.withExtension(phpExtension)
.withFileExtensionOperation(
FileExtensionOperation.SUFFIX_ORIGINAL_EXTENSION)
.build());
}

/**
* Provides the list of FileInformationProvider for PHP provided the baseFileName and list of
* php extension variants
*
* @param baseFileName, base file name of uploaded php file.
* @param phpExtension, extension of the uploaded php file.
* @return list of FileInformationProvider for PHP
*/
static List<FileInformationProvider> getFileInformationProvidersPHP(
String baseFileName, List<String> extensions) {
List<FileInformationProvider> fileInformationProviders = new ArrayList<>();
for (String extension : extensions) {
fileInformationProviders.addAll(
getFileInformationProvidersByExtensionPHP(baseFileName, extension));
}
return fileInformationProviders;
}
}
24 changes: 17 additions & 7 deletions src/main/java/org/sasanlabs/fileupload/attacks/AttackVector.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ public abstract class AttackVector {

private static final Logger LOGGER = LogManager.getLogger(AttackVector.class);

/**
* @param httpMsg, HttpMessage containing uploaded file's request and response
* @param fileName, uploaded file's name
* @param sendAndRecieveHttpMsg, consumer to send and Receive {@code HttpMessage}
* @return URI of the uploaded file
* @throws FileUploadException, in case of failure in retrieval of uploaded file.
*/
protected URI getUploadedFileURI(
HttpMessage httpMsg,
String fileName,
ConsumerWithException<HttpMessage, IOException> sendAndRecieveHttpMsg)
throws FileUploadException {
return new URILocatorImpl()
.get(httpMsg, fileName, (httpmessage) -> sendAndRecieveHttpMsg.accept(httpmessage));
}

/**
* In general, for file upload functionalities, file is uploaded from one endpoint and retrieved
* from another endpoint. This method finds the url of the file retrieval endpoint, invokes that
Expand All @@ -65,13 +81,7 @@ private HttpMessage getUploadedFileHttpMessage(
HttpMessage uploadedFileRetrievalMsg = new HttpMessage();
URI uri;
try {
uri =
new URILocatorImpl()
.get(
httpMsg,
fileName,
(httpmessage) -> sendAndRecieveHttpMsg.accept(httpmessage));

uri = this.getUploadedFileURI(httpMsg, fileName, sendAndRecieveHttpMsg);
if (Objects.isNull(uri)) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
import org.parosproxy.paros.network.HttpMessage;
import org.sasanlabs.fileupload.FileUploadScanRule;
import org.sasanlabs.fileupload.attacks.antivirus.EicarAntivirusTestFileUpload;
import org.sasanlabs.fileupload.attacks.apache.htaccess.HTAccessFileUpload;
import org.sasanlabs.fileupload.attacks.rce.jsp.ImageWithJSPSnippetFileUpload;
import org.sasanlabs.fileupload.attacks.rce.jsp.SimpleJSPFileUpload;
import org.sasanlabs.fileupload.attacks.rce.jsp.SimpleJSPXFileUpload;
import org.sasanlabs.fileupload.attacks.rce.php.ImageWithPHPSnippetFileUpload;
import org.sasanlabs.fileupload.attacks.rce.php.SimplePHPFileUpload;
import org.sasanlabs.fileupload.attacks.xss.HtmlFileUpload;
import org.sasanlabs.fileupload.attacks.xss.SVGFileUpload;
import org.sasanlabs.fileupload.exception.FileUploadException;
Expand All @@ -48,6 +51,9 @@ public class FileUploadAttackExecutor {
new SimpleJSPFileUpload(),
new SimpleJSPXFileUpload(),
new ImageWithJSPSnippetFileUpload(),
new HTAccessFileUpload(),
new SimplePHPFileUpload(),
new ImageWithPHPSnippetFileUpload(),
new EicarAntivirusTestFileUpload());

public FileUploadAttackExecutor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright 2021 SasanLabs
*
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sasanlabs.fileupload.attacks.apache.htaccess;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.parosproxy.paros.network.HttpMessage;
import org.sasanlabs.fileupload.FileUploadUtils;
import org.sasanlabs.fileupload.attacks.AttackVector;
import org.sasanlabs.fileupload.attacks.FileUploadAttackExecutor;
import org.sasanlabs.fileupload.attacks.model.FileInformationProvider;
import org.sasanlabs.fileupload.attacks.model.SimpleFileInformationProvider;
import org.sasanlabs.fileupload.attacks.model.VulnerabilityType;
import org.sasanlabs.fileupload.exception.FileUploadException;
import org.sasanlabs.fileupload.function.ConsumerWithException;
import org.sasanlabs.fileupload.matcher.ContentMatcher;
import org.sasanlabs.fileupload.matcher.impl.ContainsExpectedValueMatcher;

/**
* Important documents or links to learn about .htaccess file:
*
* <ol>
* <li>https://serverfault.com/questions/780459/load-time-impact-of-htaccess
* <li>https://cwiki.apache.org/confluence/display/HTTPD/Htaccess
* <li>https://www.nginx.com/resources/wiki/start/topics/examples/likeapache-htaccess/
* <li>https://www.danielmorell.com/guides/htaccess-seo/basics/dont-use-htaccess-unless-you-must
* </ol>
*
* @author KSASAN [email protected]
*/
public class HTAccessFileUpload extends AttackVector {

private static final String FILE_NAME = ".htaccess";
private static final String NULL_BYTE_APPENDED_FILE_NAME =
FILE_NAME + FileUploadUtils.NULL_BYTE_CHARACTER;
private static final String HEX_NULL_BYTE_APPENDED_FILE_NAME = FILE_NAME + "%00";
private static final List<FileInformationProvider> FILE_PARAMETERS =
Arrays.asList(
originalFileName -> FILE_NAME,
new SimpleFileInformationProvider(fn -> FILE_NAME, ct -> "text/plain"),
originalFileName ->
NULL_BYTE_APPENDED_FILE_NAME
+ FileUploadUtils.prefixExtensionWithPeriodCharacter(
FileUploadUtils.getExtension(originalFileName)),
new SimpleFileInformationProvider(
originalFileName ->
NULL_BYTE_APPENDED_FILE_NAME
+ FileUploadUtils.prefixExtensionWithPeriodCharacter(
FileUploadUtils.getExtension(originalFileName)),
ct -> "text/html"),
originalFileName ->
HEX_NULL_BYTE_APPENDED_FILE_NAME
+ FileUploadUtils.prefixExtensionWithPeriodCharacter(
FileUploadUtils.getExtension(originalFileName)),
new SimpleFileInformationProvider(
originalFileName ->
HEX_NULL_BYTE_APPENDED_FILE_NAME
+ FileUploadUtils.prefixExtensionWithPeriodCharacter(
FileUploadUtils.getExtension(originalFileName)),
ct -> "text/plain"));
private static final String EXPECTED_CONTENT = "Index of /";
private static final ContentMatcher CONTENT_MATCHER =
new ContainsExpectedValueMatcher(EXPECTED_CONTENT);
private static final String HTACCESS_FILE_CONTENT = "Options +Indexes";

@Override
protected URI getUploadedFileURI(
HttpMessage httpMsg,
String fileName,
ConsumerWithException<HttpMessage, IOException> sendAndRecieveHttpMsg)
throws FileUploadException {
URI uri = super.getUploadedFileURI(httpMsg, fileName, sendAndRecieveHttpMsg);
try {
if (uri != null && uri.getPath().lastIndexOf(FileUploadUtils.SLASH) != -1) {

uri.setPath(
uri.getPath()
.substring(
0, uri.getPath().lastIndexOf(FileUploadUtils.SLASH) + 1));
}
} catch (URIException e) {
throw new FileUploadException("Unable to set the URI fragment", e);
}
return uri;
}

@Override
public boolean execute(FileUploadAttackExecutor fileUploadAttackExecutor)
throws FileUploadException {
return this.genericAttackExecutor(
fileUploadAttackExecutor,
HTACCESS_FILE_CONTENT,
FILE_PARAMETERS,
CONTENT_MATCHER,
VulnerabilityType.HTACCESS_FILE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2021 SasanLabs
*
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sasanlabs.fileupload.attacks.model;

import java.util.function.Function;

/** @author KSASAN [email protected] */
public class SimpleFileInformationProvider implements FileInformationProvider {

private Function<String, String> fileNameFunction;
private Function<String, String> contentTypeFunction;

public SimpleFileInformationProvider(
Function<String, String> fileNameFunction,
Function<String, String> contentTypeFunction) {
this.fileNameFunction = fileNameFunction;
this.contentTypeFunction = contentTypeFunction;
}

@Override
public String getFileName(String originalFileName) {
return this.fileNameFunction.apply(originalFileName);
}

@Override
public String getContentType(String originalContentType) {
return this.contentTypeFunction.apply(originalContentType);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ public enum VulnerabilityType {
XSS_HTML_FILE("xssHtmlFile", Alert.RISK_MEDIUM),
XSS_SVG_FILE("xssSvgFile", Alert.RISK_MEDIUM),
RCE_PHP_FILE("rcePhpFile", Alert.RISK_HIGH),
RCE_GIF_PHP_FILE("rceGifPhpFile", Alert.RISK_HIGH),
RCE_JPEG_PHP_FILE("rceJpegPhpFile", Alert.RISK_HIGH),
RCE_JSP_FILE("rceJspFile", Alert.RISK_HIGH),
RCE_GIF_JSP_FILE("rceGifJspFile", Alert.RISK_HIGH),
RCE_JPEG_JSP_FILE("rceJpegJspFile", Alert.RISK_HIGH),
RCE_JSPX_FILE("rceJspxFile", Alert.RISK_HIGH),
EICAR_FILE("antiVirusEicarFile", Alert.RISK_MEDIUM);
EICAR_FILE("antiVirusEicarFile", Alert.RISK_MEDIUM),
HTACCESS_FILE("htaccessFile", Alert.RISK_HIGH);

private String messageKey;
private int alertLevel;
Expand Down
Loading

0 comments on commit b961173

Please sign in to comment.