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
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; |
|
} |
|
}
|
|
|