package com.nvarghese.beowulf.sfe.webtest.tm.infoleak; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.nvarghese.beowulf.common.http.txn.AbstractHttpTransaction; import com.nvarghese.beowulf.common.http.txn.HttpMethodType; import com.nvarghese.beowulf.common.http.txn.HttpTransactionFactory; import com.nvarghese.beowulf.common.http.txn.HttpTxnDAO; import com.nvarghese.beowulf.common.http.txn.HttpTxnDocument; import com.nvarghese.beowulf.common.http.txn.TransactionSource; import com.nvarghese.beowulf.common.scan.dao.ReportIssueDAO; import com.nvarghese.beowulf.common.scan.model.ReportIssueDocument; import com.nvarghese.beowulf.common.scan.model.ReportIssueVariantDocument; import com.nvarghese.beowulf.common.utils.HttpUtils; import com.nvarghese.beowulf.common.webtest.ReportThreatType; import com.nvarghese.beowulf.common.webtest.ThreatSeverityType; import com.nvarghese.beowulf.sfe.webtest.tm.AbstractTestModule; import com.nvarghese.beowulf.sfe.webtest.types.DirectoryTestType; public class PhpInfoPage extends AbstractTestModule implements DirectoryTestType { static Logger logger = LoggerFactory.getLogger(PhpInfoPage.class); @Override public void testByDirectory(ObjectId txnObjId, String directory) { logger.info("Running the PhpInfoPage module for the txnObjId: {} with data store: {}", txnObjId.toString(), scanInstanceDataStore.getDB() .getName()); HttpTxnDAO txnDAO = new HttpTxnDAO(scanInstanceDataStore); HttpTxnDocument httpTxnDocument = txnDAO.getHttpTxnDocument(txnObjId); if (httpTxnDocument == null) return; AbstractHttpTransaction originalTxn = AbstractHttpTransaction.getObject(httpTxnDocument); List<String> phpInfoNames = loadDefaultPhpInfoNames(); for (String phpInfoName : phpInfoNames) { try { URI uri = new URI(originalTxn.getHostUriWithoutTrailingSlash() + directory + phpInfoName + "/"); AbstractHttpTransaction testTransaction = HttpTransactionFactory.createTransaction(HttpMethodType.GET, uri, null, originalTxn .getURI().toString(), TransactionSource.TEST); testTransaction.execute(); if (HttpUtils.fileExists(testTransaction.getResponseStatusCode())) { logger.info("Detected directory `{}` by test", uri.getPath()); ObjectId id = txnDAO.createHttpTxnDocument(testTransaction.toHttpTxnDocument()); testTransaction.setObjId(id); reportFinding(originalTxn, testTransaction, phpInfoName); } else { } } catch (URISyntaxException e) { logger.error("Failed to execute the request. Reason: {}", e.getMessage(), e); } } } private void reportFinding(AbstractHttpTransaction originalTxn, AbstractHttpTransaction testTransaction, String phpInfoName) { String responseBodyText = testTransaction.getResponseBodyAsString(); Map<String, String> infoMap = testAndRecordPattern(responseBodyText); String uri = testTransaction.getURI().toString(); ReportIssueDAO issueDAO = new ReportIssueDAO(scanInstanceDataStore); ReportIssueDocument reportIssueDocument = issueDAO.findByUrlAndThreatTypeAndModuleNumber(uri, ReportThreatType.PRED_RESOURCE_LOCATION, moduleNumber, false); if (reportIssueDocument == null) { reportIssueDocument = new ReportIssueDocument(); reportIssueDocument.setThreatSeverityType(ThreatSeverityType.MEDIUM); reportIssueDocument.setThreatType(ReportThreatType.PRED_RESOURCE_LOCATION); reportIssueDocument.setIssueUrl(uri); reportIssueDocument.setModuleName(moduleName); reportIssueDocument.setModuleNumber(moduleNumber); // reasoning reportIssueDocument.setReasoning("A file containing phpinfo() details was detected."); // remediation reportIssueDocument .setRemediation("Although this is not a vulnerability by itself and are often used for testing and debugging purpose, however " + "it is recommended not to expose such information on a production application since it can be used for staging " + "other attacks."); // references reportIssueDocument.setReferences("perishablepress - http://perishablepress.com/press/2010/03/17/htaccess-secure-phpinfo-php/"); issueDAO.createReportIssueDocument(reportIssueDocument); } // set issue variant ReportIssueVariantDocument issueVariant = new ReportIssueVariantDocument(); String description = "A file serving details using PHP interpreter's built-in phpinfo() function was detected on the remote server.</br>"; description += "Following details were extracted: </br></br>"; description += "  System: " + infoMap.get("System") + "</br>"; if (infoMap.containsKey("BuildDate")) { description += "  Build Date: " + infoMap.get("BuildDate") + "</br>"; } if (infoMap.containsKey("DocumentRoot")) { description += "  Document Root: " + infoMap.get("DocumentRoot") + "</br>"; } if (infoMap.containsKey("ScriptFileName")) { description += "  Script File Name: " + infoMap.get("ScriptFileName") + "</br>"; } issueVariant.setDescription(description); issueVariant.setOrigicalTxn(originalTxn.getObjId()); issueVariant.setTestTxn(testTransaction.getObjId()); issueDAO.addReportIssueVariants(reportIssueDocument.getId(), issueVariant); } private Map<String, String> testAndRecordPattern(String responseBodyText) { Map<String, String> infoMap = new HashMap<String, String>(1); /* * We attempt to extract the System: XXX field since it is common in all * version */ Pattern p = Pattern.compile("System\\s+<\\/td><td.*>(.*)<\\/td>"); Matcher m = p.matcher(responseBodyText); if (!m.find()) { return infoMap; } else { infoMap.put("System", m.group(1)); } /* * Extract other interesting fields */ p = Pattern.compile("Build Date\\s+<\\/td><td.*>(.*)<\\/td>"); m = p.matcher(responseBodyText); if (m.find()) { infoMap.put("BuildDate", m.group(1)); } p = Pattern.compile("DOCUMENT_ROOT\\s+<\\/td><td.*>(.*)<\\/td>"); m = p.matcher(responseBodyText); if (m.find()) { infoMap.put("DocumentRoot", m.group(1)); } p = Pattern.compile("SCRIPT_FILENAME\\s+<\\/td><td.*>(.*)<\\/td>"); m = p.matcher(responseBodyText); if (m.find()) { infoMap.put("ScriptFileName", m.group(1)); } return infoMap; } private List<String> loadDefaultPhpInfoNames() { List<String> names = new ArrayList<String>(); names.add("info.php"); names.add("phpinfo.php"); names.add("php_info.php"); names.add("system.php"); names.add("sysinfo.php"); names.add("test.php"); return names; } }