SuperX-Kernmodul
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

200 lines
9.5 KiB

package de.superx.servlet;
import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import de.superx.BaseDbTest;
import de.superx.TestApplicationConfigPg;
import de.superx.bin.ExecuteMask;
import de.superx.common.UngueltigeEingabeException;
import de.superx.sec.InputCheckRegistry;
import de.superx.util.UnittestAppender;
//@ContextConfiguration(classes = { TestApplicationConfigPg.class })
public class SQLInjectionTest extends BaseDbTest {
Logger logger = Logger.getLogger(ExecuteMask.class);
private static String outputFilename;
@BeforeClass
public static void setup() throws Exception {
outputFilename = String.join(File.separator, "test", "output_160440.html");
InputCheckRegistry.registerDefaultChecks();
ExecuteMask.setMyWEBINFFilePath(SuperXManager.getWEB_INFPfad());
}
@SuppressWarnings("static-method")
@Test
public void testExpansionInjection() throws Exception {
String[] params = buildParamsStudierendeDatenblatt("123)@@SEMICOLON@@insert into xdummy(c) values (2)@@SEMICOLON@@commit@@SEMICOLON@@--");
Logger sxsql_ServerLogger = setUpLogger();
UnittestAppender ta = setUpTestAppender(sxsql_ServerLogger);
try (Connection conn = SxPools.get(MANDANTEN_ID).getConnection()) {
String entries_before = getXDummyEntries(conn);
boolean bla = setFieldAttribute(conn, 160462, "{InputCheck:disabled}");
try {
ExecuteMask.execute(params);
} catch (Exception e) {
// as most exceptions are not throws, but caught and written to log, we have to analize the log...
}
assertThatInjectionAttemptCausedSQLException(ta);
assertThatSQLExceptionWasNotCausedByFaultyTestEnvironment(ta);
String entries_after = getXDummyEntries(conn);
Assert.assertEquals("Count of 'xdummy' entries should not have changed, when injection is prevented", entries_before, entries_after);
}
}
@SuppressWarnings("static-method")
@Test
public void testDatabaseFunctionInjection() throws Exception {
String[] params = buildParamsStudierendeDatenblatt("sp_get_keylist_str($$bla1$$,$$bla2$$)::INT");
try (Connection conn = SxPools.get(MANDANTEN_ID).getConnection()) {
boolean bla = setFieldAttribute(conn, 160462, "{InputCheck:matrikelnummern}");
}
executeMaskAndValidateExpectedOutcome(params);
}
@SuppressWarnings("static-method")
@Test
public void testMultilineCommentInjection() throws Exception {
String[] params = buildParamsVerwundbarkeitstest("bla\nblub", "");
try (Connection conn = SxPools.get(MANDANTEN_ID).getConnection()) {
prepareVerwundbarbeitstest(conn);
}
executeMaskAndValidateExpectedOutcome(params);
}
@SuppressWarnings("static-method")
@Test
public void testZombieParameterInjection() throws Exception {
String[] params = buildParamsVerwundbarkeitstest("", "ksjasf");
try (Connection conn = SxPools.get(MANDANTEN_ID).getConnection()) {
prepareVerwundbarbeitstest(conn);
}
executeMaskAndValidateExpectedOutcome(params);
}
private static void assertThatSQLExceptionWasNotCausedByFaultyTestEnvironment(UnittestAppender ta) {
String message = null;
boolean log_events_contain_errors_due_to_missing_db_functions = false;
for (LoggingEvent event : ta.loggingEventList) {
message = event.getMessage().toString();
if (message != null && message.contains("Fehler beim Aufruf eine benutzerdefinierten Funktion")) {
log_events_contain_errors_due_to_missing_db_functions = true;
break;
}
}
Assert.assertFalse("Found error message:\n" + message, log_events_contain_errors_due_to_missing_db_functions);
}
private static void assertThatInjectionAttemptCausedSQLException(UnittestAppender ta) {
boolean log_events_contain_exception_with_injected_sql = false;
for (LoggingEvent event : ta.loggingEventList) {
String message = event.getMessage().toString();
if (message != null && message.contains("insert into xdummy(")) {
log_events_contain_exception_with_injected_sql = true;
break;
}
}
Assert.assertTrue("Application log should contain an SQLException due to injected SQL", log_events_contain_exception_with_injected_sql);
}
private static UnittestAppender setUpTestAppender(Logger sxsql_ServerLogger) {
UnittestAppender ta = new UnittestAppender();
sxsql_ServerLogger.addAppender(ta);
return ta;
}
private static Logger setUpLogger() {
Logger sxsql_ServerLogger = Logger.getLogger("superx_" + MANDANTEN_ID);
sxsql_ServerLogger.setAdditivity(false);
sxsql_ServerLogger.removeAllAppenders();
sxsql_ServerLogger.setLevel(org.apache.log4j.Level.ALL);
return sxsql_ServerLogger;
}
private static void executeMaskAndValidateExpectedOutcome(String[] params) {
try {
ExecuteMask.execute(params);
} catch (Exception e) {
Assert.assertTrue(
"Exception is expected to be caused by InputCheck and thus to be of type UngueltigeEingabeException",
e.getClass().equals(UngueltigeEingabeException.class)
);
}
}
private static boolean setFieldAttribute(Connection conn, int tid, String value) throws SQLException {
Statement statement = conn.createStatement();
return statement.execute("UPDATE felderinfo SET attribut = '" + value + "' WHERE tid = " + tid + ";");
}
private static void prepareVerwundbarbeitstest(Connection conn) throws SQLException {
try (Statement statement = conn.createStatement()) {
// extend the mask-SQL with
// 1. the /* <SQL-snippet> -- <<HTTP-Parameter>>*/ pattern, which is quite common in superx
// 2. a parameter placeholder, which has no configuration in table 'felderinfo' ('zombie_parameter')
statement.execute("UPDATE maskeninfo mi SET select_stmt = replace(select_stmt, 'where ', " +
"E'WHERE\\n/* 1 = 1 AND -- <<Inhalt2>>*/\\n/* <<zombie_parameter>> AND */') " +
"WHERE mi.name = 'Verwundbarkeitstest';");
// replace the GROUP BY clauses with versions that also work in H2 (should be rolled out in the unl-file as well)
statement.execute("UPDATE felderinfo SET relation = replace(relation, 'group by 1', 'group by buchungsab_fb') " +
"WHERE name = 'Buchungsab_fb' AND tid IN" +
"(SELECT felderinfo_id FROM masken_felder_bez mfb JOIN maskeninfo mi ON mfb.maskeninfo_id = mi.tid AND mi.name = 'Verwundbarkeitstest');");
statement.execute("UPDATE felderinfo SET relation = replace(relation, 'group by 1', 'group by projnr_ins') " +
"WHERE name = 'Projnr_ins' AND tid IN" +
"(SELECT felderinfo_id FROM masken_felder_bez mfb JOIN maskeninfo mi ON mfb.maskeninfo_id = mi.tid AND mi.name = 'Verwundbarkeitstest');");
statement.execute("UPDATE felderinfo SET zeilenanzahl = 1 " +
"WHERE name = 'Inhalt2' AND tid IN" +
"(SELECT felderinfo_id FROM masken_felder_bez mfb JOIN maskeninfo mi ON mfb.maskeninfo_id = mi.tid AND mi.name = 'Verwundbarkeitstest');");
}
}
private static String getXDummyEntries(Connection conn) throws SQLException {
String entries = "";
try (Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery("select count(*) entries from xdummy")) {
resultSet.next();
entries = resultSet.getString("entries");
}
return entries;
}
private static String[] buildParamsStudierendeDatenblatt(String injection) {
String contenttype = "";
String stylesheet = "tabelle_html_datenblatt.xsl";
String[] params = { "-tid:160440", "-out:" + outputFilename, "-user:admin",
"-params:Köpfe oder Fälle ?=KoF_2&Stichtag=1"
+ "&Matrikel-Nr.=" + injection
+ "&stylesheet=" + stylesheet + "&tablestylesheet=" + stylesheet + "&contenttype="
+ contenttype,
"-logger:" + logConfigPath, "-mandantenID:" + MANDANTEN_ID };
return params;
}
private static String[] buildParamsVerwundbarkeitstest(String multiline_injection_payload, String zombie_param_injection_payload) {
String contenttype = "";
String stylesheet = "tabelle_html_datenblatt.xsl";
String[] params = { "-tid:71580", "-out:" + outputFilename, "-user:admin",
"-params:Inhalt=test"
+ "&Inhalt2=" + multiline_injection_payload
+ "zombie_parameter=" + zombie_param_injection_payload
+ "&Jahr=2018&stylesheet=" + stylesheet
+ "&tablestylesheet=" + stylesheet
+ "&contenttype=" + contenttype,
"-logger:" + logConfigPath, "-mandantenID:" + MANDANTEN_ID };
return params;
}
}