All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rivulet.internal.fuzz.generator.SqlInjectionGenerator Maven / Gradle / Ivy

The newest version!
package io.rivulet.internal.fuzz.generator;

import edu.columbia.cs.psl.phosphor.runtime.Taint;
import io.rivulet.internal.ParseUtils;
import io.rivulet.internal.Violation;
import io.rivulet.internal.rerun.TestRerunConfiguration;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;

import java.util.*;

public class SqlInjectionGenerator extends RerunGeneratorWrapper {

	// Base sink methods targeted by this type of generator
	private static final List targetedSinks = Arrays.asList(
			"java/sql/Statement.executeQuery(Ljava/lang/String;)Ljava/sql/ResultSet;",
			"java/sql/Statement.execute(Ljava/lang/String;)Z",
			"java/sql/Statement.execute(Ljava/lang/String;I)Z",
			"java/sql/Statement.execute(Ljava/lang/String;[I)Z",
			"java/sql/Statement.execute(Ljava/lang/String;[Ljava/lang/String;)Z",
			"java/sql/Statement.executeUpdate(Ljava/lang/String;)I",
			"java/sql/Statement.executeUpdate(Ljava/lang/String;I)I",
			"java/sql/Statement.executeUpdate(Ljava/lang/String;[I)I",
			"java/sql/Statement.executeUpdate(Ljava/lang/String;[Ljava/lang/String;)I",
			"java/sql/Statement.executeLargeUpdate(Ljava/lang/String;)J",
			"java/sql/Statement.executeLargeUpdate(Ljava/lang/String;I)J",
			"java/sql/Statement.executeLargeUpdate(Ljava/lang/String;[I)J",
			"java/sql/Statement.executeLargeUpdate(Ljava/lang/String;[Ljava/lang/String;)J",
			"java/sql/Statement.addBatch(Ljava/lang/String;)V",
			"java/sql/Connection.prepareCall(Ljava/lang/String;)Ljava/sql/CallableStatement;",
			"java/sql/Connection.prepareCall(Ljava/lang/String;II)Ljava/sql/CallableStatement;",
			"java/sql/Connection.prepareCall(Ljava/lang/String;III)Ljava/sql/CallableStatement;",
			"java/sql/Connection.prepareStatement(Ljava/lang/String;)Ljava/sql/PreparedStatement;",
			"java/sql/Connection.prepareStatement(Ljava/lang/String;I)Ljava/sql/PreparedStatement;",
			"java/sql/Connection.prepareStatement(Ljava/lang/String;II)Ljava/sql/PreparedStatement;",
			"java/sql/Connection.prepareStatement(Ljava/lang/String;III)Ljava/sql/PreparedStatement;",
			"java/sql/Connection.prepareStatement(Ljava/lang/String;[I)Ljava/sql/PreparedStatement;",
			"java/sql/Connection.prepareStatement(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;",
			"java/sql/Connection.nativeSQL(Ljava/lang/String;)Ljava/lang/String;"
	);
	// Common SQL keywords
	private static final List sqlKeywords = Arrays.asList("ADD", "ALTER", "ALL", "AND", "ANY", "AS", "ASC",
			"BACKUP", "BETWEEN", "BULK", "CASE", "CHECK", "COLUMN", "CONSTRAINT", "CREATE", "DATABASE", "DEFAULT",
			"DELETE", "DESC", "DISTINCT", "DROP", "EXEC", "EXISTS", "GROUP", "HAVING", "IN", "INDEX", "INNER", "INSERT",
			"INTO", "IS", "JOIN", "KEY", "LIKE", "LIMIT", "NOT", "NULL", "OR", "ORDER", "OUTER", "PROCEDURE", "ROWNUM",
			"SELECT", "SET", "TABLE", "TOP", "TRUNCATE", "UNION", "UNIQUE", "UPDATE", "VALUES", "VIEW", "WHERE"
	);
	// Regex for parts of original values that should be removed before adding the original value into a payload
	private static final String REMOVE_REGEX = "['\"`#]|(--)|(/\\*)|(\\*/)";

	/* Returns builders with SQL injection payloads inspired by
	 * https://www.owasp.org/index.php/Testing_for_SQL_Injection_(OTG-INPVAL-005) that leverage information about the
	 * context of the tainted values in the SQL statement. */
	@Override
	public Collection generateReruns(Violation violation, Object receiver, Object[] arguments)  {
		if(arguments == null || arguments.length == 0 || !(arguments[0] instanceof String)) {
			return new LinkedList<>();
		}
		LinkedHashSet configs = new LinkedHashSet<>();
		String sql = (String) arguments[0];
		List> parts = ParseUtils.parseSQL(sql);
		for(String codeChunk : parts.get(0)) {
			generateRerunsForUncommentedChunk(codeChunk, violation, configs, "");
		}
		for(String commentedChunk : parts.get(1)) {
			generateRerunsForCommentedChunk(commentedChunk, violation, configs);
		}
		for(String quotedChunk : parts.get(2)) {
			generateRerunsForUncommentedChunk(quotedChunk, violation, configs, quotedChunk.charAt(0) + "");
		}
		try {
			for(LikeExpression expression : findLikeExpressions(sql)) {
				generateRerunsForLikeExpression(expression, violation, configs);
			}
		} catch(JSQLParserException e) {
			// Couldn't parse to check LIKE expressions; SQL is probably malformed. Replace tainted section with
			// stand-in values and retry parsing.
			String retrySql = replaceTaintedChunksWithStandIns(sql);
			try {
				for(LikeExpression expression : findLikeExpressions(retrySql)) {
					generateRerunsForLikeExpression(expression, violation, configs);
				}
			} catch(JSQLParserException ex) {
				// SQL is still malformed
			}
		}
		return configs;
	}

	/* Adds rerun configurations to the specified set for the specified chunk of uncommented SQL. */
	private void generateRerunsForUncommentedChunk(String chunk, Violation originalViolation,
												   LinkedHashSet configs, String quote) {
		if(containsTaintedChar(chunk)) {
			List payloads = Arrays.asList(
					String.format("%s%s; DROP TABLE users; --", "%s", quote),
					String.format("%s%s UNION SELECT table_name, 's', '1' FROM information_scheme.tables --", "%s", quote),
					String.format("%s%s/**/OR/**/true--", "%s", quote)
			);
			configs.addAll(generateRerunsUsingTaintedChunks(originalViolation, chunk, payloads,0, REMOVE_REGEX));
		}
	}

	/* Adds rerun configurations to the specified set for the specified tainted chunk of commented SQL. */
	private void generateRerunsForCommentedChunk(String chunk, Violation originalViolation, LinkedHashSet configs) {
		if(containsTaintedChar(chunk)) {
			String commentExit = chunk.startsWith("/*") ? "*/" : "\n";
			String commentStart = (chunk.startsWith("#") || chunk.length() < 2) ? "#" : chunk.substring(0, 2);
			List payloads = Arrays.asList(
					String.format("%s %s DROP TABLE users %s", "%s", commentExit, commentStart),
					String.format("%s %s ; DROP TABLE users; %s", "%s", commentExit, commentStart)
			);
			configs.addAll(generateRerunsUsingTaintedChunks(originalViolation, chunk, payloads,0, REMOVE_REGEX));
		}
	}

	/* Adds rerun configurations to the specified set for the specified tainted like expression. */
	private void generateRerunsForLikeExpression(LikeExpression expression, Violation originalViolation, LinkedHashSet configs) {
		List payloads = Arrays.asList(
				"%s%%a%%%%%%%%",
				"%sa_____a"
		);
		configs.addAll(generateRerunsUsingTaintedChunks(originalViolation, expression.getRightExpression().toString(),
				payloads,0, REMOVE_REGEX));
	}

	/* Reports a violation if a tainted, unescaped, uncommented SQL keyword reached a targeted SQL sink. */
	@Override
	public void checkTaint(Object obj, int argIndex, Violation violation) {
		if(obj instanceof String) {
			String sql = (String) obj;
			List code = ParseUtils.parseSQL(sql).get(0);
			// Check for unquoted and and uncommented tainted SQL keywords
			for(String chunk : code) {
				String[] tokens = chunk.toUpperCase().split("\\s+");
				for(String token : tokens) {
					if(sqlKeywords.contains(token) && containsTaintedChar(token)) {
						super.checkTaint(sql, argIndex, violation, false);
						return;
					}
				}
			}
			// Check for tainted, unescaped wildcards in LIKE clauses
			try {
				for(LikeExpression expression : findLikeExpressions(sql)) {
					if(checkLikeExpression(expression)) {
						super.checkTaint(sql, argIndex, violation, false);
						return;
					}
				}
			} catch(JSQLParserException e) {
				// Couldn't parse to check LIKE expressions; SQL is probably malformed.
			}
		}
	}

	/* Returns whether the specified LIKE clause contains a tainted, unescaped wildcard. */
	private boolean checkLikeExpression(LikeExpression expression) {
		String value = expression.getRightExpression().toString();
		if(containsTaintedChar(value)) {
			String escape = expression.getEscape();
			if(escape == null || escape.length() == 0) {
				// Use default escape character
				escape = "\\";
			}
			// Removed escaped characters from the value
			while(value.contains(escape)) {
				int index = value.indexOf(escape);
				int end = index + escape.length();
				if(end < value.length()) {
					end ++;
				}
				value = value.substring(0, index) + value.substring(end);
			}
			Taint[] tags = getStringValueTaints(value);
			if(tags != null) {
				for(int i = 0; i < tags.length; i++) {
					if((value.charAt(i) == '%' || value.charAt(i) == '_') && tags[i] != null && !tags[i].isEmpty()) {
						// Tainted, unescaped wildcard found
						return true;
					}
				}
			}
		}
		return false;
	}

	/* Adds any like expressions found in the specified SELECT body to the specified list. */
	private void findLikeExpressions(SelectBody body, List expressions) {
		if(body instanceof PlainSelect) {
			PlainSelect select = (PlainSelect) body;
			Expression where = select.getWhere();
			if(where instanceof LikeExpression) {
				expressions.add((LikeExpression) where);
			}
		} else if(body instanceof SetOperationList) {
			SetOperationList setOpList = (SetOperationList) body;
			for(SelectBody el : setOpList.getSelects()) {
				findLikeExpressions(el, expressions);
			}
		}
	}

	/* Returns a list containing all of the LIKE expressions in the specified SQL String. */
	private List findLikeExpressions(String sql) throws JSQLParserException {
		List expressions = new LinkedList<>();
		// Check for SELECT statements with WHERE clauses with tainted LIKE expression
		List statements = CCJSqlParserUtil.parseStatements(sql).getStatements();
		for(Statement statement : statements) {
			if(statement instanceof Select) {
				SelectBody body = ((Select)statement).getSelectBody();
				findLikeExpressions(body, expressions);
			}
		}
		return expressions;
	}

	@Override
	public List getTargetedBaseSinks() {
		return targetedSinks;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy