private boolean isValidMessageToScan(HttpMessage msg) { if (getScannerOptions().isScanHeadersAllRequests()) { return true; } // First we check if it's a dynamic or static page // I'd to do this because scanning starts to be veeeeery slow // -- // this is a trivial implementation, should be good to have // a page dynamic check at the parent plugin level which should // use or not Variants according to the behavior of the request // (e.g. different content or status error/redirect) String query = null; try { query = msg.getRequestHeader().getURI().getQuery(); } catch (URIException e) { log.error(e.getMessage(), e); } // If there's almost one GET parameter go ahead if (query == null || query.isEmpty()) { // If also the Request body is null maybe it's a static page oer a null parameter page if (msg.getRequestBody().length() == 0) { return false; } } return true; }
@Override public boolean onHttpRequestSend(HttpMessage message) { if (isActive() && SAMLUtils.hasSAMLMessage(message)) { try { SAMLMessage samlMessage = new SAMLMessage(message); // change the params for (Attribute attribute : configuration.getAutoChangeAttributes()) { String value = attribute.getValue().toString(); boolean changed = samlMessage.changeAttributeValueTo(attribute.getName(), value); if (changed) { log.debug(attribute.getName() + ": value changed to " + value); } } // change the original message HttpMessage changedMessege = samlMessage.getChangedMessage(); if (changedMessege != message) { // check for reference, if they are same the message is already changed, // else the header and body are changed message.setRequestBody(changedMessege.getRequestBody()); message.setRequestHeader(changedMessege.getRequestHeader()); } } catch (SAMLException ignored) { } } return true; }
@Override public void scanHttpResponseReceive(HttpMessage msg, int id, Source source) { if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) { return; } String responseBody = msg.getRequestBody().toString(); if (responseBody == null) { return; } Set<HtmlParameter> params = new TreeSet<>(msg.getFormParams()); params.addAll(msg.getUrlParams()); if (params.size() == 0) { return; } if (!isResponseHTML(msg, source) && !isResponseXML(source)) { return; } if (isResponseHTML(msg, source)) { checkMetaContentCharset(msg, id, source, params); } else if (isResponseXML(source)) { checkXmlEncodingCharset(msg, id, source, params); } checkContentTypeCharset(msg, id, params); }
private void displayMessage(SearchResult sr) { HttpMessage msg = sr.getMessage(); if (msg.getRequestHeader().isEmpty()) { requestPanel.clearView(true); } else { requestPanel.setMessage(msg); } if (msg.getResponseHeader().isEmpty()) { responsePanel.clearView(false); } else { responsePanel.setMessage(msg, true); } highlightFirstResult(sr); }
/** * @param msg * @param originalPair * @param name * @param value * @param escaped * @return */ private String setParameter( HttpMessage msg, NameValuePair originalPair, String name, String value, boolean escaped) { // Here gives null pointer exception... // maybe bacause the name value isn't equal to the original value one msg.getRequestHeader().setHeader(originalPair.getName(), value); return name + ":" + value; }
// TODO: these methods have been extracted from CharsetMismatchScanner // I think we should create helper methods for them private boolean isResponseHTML(HttpMessage message, Source source) { String contentType = message.getResponseHeader().getHeader(HttpHeader.CONTENT_TYPE); if (contentType == null) { return false; } return contentType.indexOf("text/html") != -1 || contentType.indexOf("application/xhtml+xml") != -1 || contentType.indexOf("application/xhtml") != -1; }
private void checkContentTypeCharset(HttpMessage msg, int id, Set<HtmlParameter> params) { String charset = msg.getResponseHeader().getCharset(); if (charset == null || charset.equals("")) { return; } for (HtmlParameter param : params) { if (charset.equalsIgnoreCase(param.getValue())) { raiseAlert(msg, id, "Content-Type HTTP header", "charset", param, charset); } } }
/** @param msg */ @Override public void setMessage(HttpMessage msg) { if (!isValidMessageToScan(msg)) { return; } // httpHeaders is never null, so no need to check for null List<HttpHeaderField> httpHeaders = msg.getRequestHeader().getHeaders(); int headerPos = 0; for (HttpHeaderField header : httpHeaders) { if (!NON_INJECTABLE_HEADERS.contains(header.getName().toLowerCase(Locale.ROOT))) { params.add( new NameValuePair( NameValuePair.TYPE_HEADER, header.getName(), header.getValue(), headerPos++)); } } }
private void raiseAlert( HttpMessage msg, int id, String tag, String attr, HtmlParameter param, String charset) { Alert alert = new Alert(getPluginId(), Alert.RISK_MEDIUM, Alert.WARNING, getName()); alert.setDetail( getDescriptionMessage(), msg.getRequestHeader().getURI().toString(), param.getName(), getExploitMessage(msg), getExtraInfoMessage(msg, tag, attr, param, charset), getSolutionMessage(), getReferenceMessage(), "", // No evidence 0, // TODO CWE Id 0, // TODO WASC Id msg); parent.raiseAlert(id, alert); }
@SuppressWarnings("unused") @Override public boolean parseResource(HttpMessage message, Source source, int depth) { // parse the Git index file, based on publicly available (but incomplete) documentation of the // file format, and some reverse-engineering. if (message == null || !params.isParseGit()) { return false; } log.debug("Parsing a Git resource..."); // Get the response content byte[] data = message.getResponseBody().getBytes(); String baseURL = message.getRequestHeader().getURI().toString(); try { String fullpath = message.getRequestHeader().getURI().getPath(); if (fullpath == null) fullpath = ""; if (log.isDebugEnabled()) log.debug("The full path is [" + fullpath + "]"); // make sure the file name is as expected Matcher gitIndexFilenameMatcher = gitIndexFilenamePattern.matcher(fullpath); if (gitIndexFilenameMatcher.find()) { // dealing with the Git Index file Matcher gitIndexContentMatcher = gitIndexContentPattern.matcher(new String(data)); if (gitIndexContentMatcher.find()) { // it looks like a duck, and quacks like a duck. // although it could still be an animatronic duck ByteBuffer dataBuffer = ByteBuffer.wrap(data); byte[] dircArray = new byte[4]; dataBuffer.get(dircArray, 0, 4); int indexFileVersion = dataBuffer.getInt(); if (log.isDebugEnabled()) log.debug("The Git index file version is " + indexFileVersion); int indexEntryCount = dataBuffer.getInt(); if (log.isDebugEnabled()) log.debug(indexEntryCount + " entries were found in the Git index file "); if (indexFileVersion != 2 && indexFileVersion != 3 && indexFileVersion != 4) { throw new Exception( "Only Git Index File versions 2, 3, and 4 are currently supported. Git Index File Version " + indexFileVersion + " was found."); } // for version 4 (and upwards?), we need to know the previous entry name, so store it String previousIndexEntryName = ""; for (int entryIndex = 0; entryIndex < indexEntryCount; entryIndex++) { int entryBytesRead = 0; int indexEntryCtime1 = dataBuffer.getInt(); entryBytesRead += 4; if (log.isDebugEnabled()) log.debug("Entry " + entryIndex + " has indexEntryCtime1 " + indexEntryCtime1); int indexEntryCtime2 = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryMtime1 = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryMtime2 = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryDev = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryInode = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryMode = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryUid = dataBuffer.getInt(); entryBytesRead += 4; int indexEntryGid = dataBuffer.getInt(); entryBytesRead += 4; int indexEntrySize = dataBuffer.getInt(); entryBytesRead += 4; if (log.isDebugEnabled()) log.debug("Entry " + entryIndex + " has size " + indexEntrySize); // size is unspecified for the entry id, but it seems to be 40 bytes SHA-1 string // stored as 20 bytes, network order byte[] indexEntryIdBuffer = new byte[20]; dataBuffer.get(indexEntryIdBuffer, 0, 20); entryBytesRead += 20; String indexEntryId = new String(indexEntryIdBuffer); short indexEntryFlags = dataBuffer.getShort(); entryBytesRead += 2; if (log.isDebugEnabled()) log.debug("Entry " + entryIndex + " has flags " + indexEntryFlags); // mask off all but the least significant 12 bits of the index entry flags to get the // length of the name in bytes int indexEntryNameByteLength = indexEntryFlags & (int) 4095; if (log.isDebugEnabled()) log.debug( "Entry " + entryIndex + " has a name of length " + indexEntryNameByteLength); // mask off all but the second most significant 12 bit of the index entry flags to get // the extended flag for the entry // int indexEntryExtendedFlag = indexEntryFlags & (int)16384; int indexEntryExtendedFlag = ((indexEntryFlags & (int) (1 << 14)) >> 14); if (log.isDebugEnabled()) log.debug( "Entry " + entryIndex + " has an extended flag of " + indexEntryExtendedFlag); // check that we parsed out the index entry extended flag correctly. // this is more of an assertion than anything. It's already saved my bacon once. if (indexEntryExtendedFlag != 0 && indexEntryExtendedFlag != 1) { throw new Exception( "Error parsing out the extended flag for index entry " + entryIndex + ". We got " + indexEntryExtendedFlag); } if (indexFileVersion == 2 && indexEntryExtendedFlag != 0) { throw new Exception( "Index File Version 2 is supposed to have the extended flag set to 0. For index entry " + entryIndex + ", it is set to " + indexEntryExtendedFlag); } // specific to version 3 and above, if the extended flag is set for the entry. if (indexFileVersion > 2 && indexEntryExtendedFlag == 1) { if (log.isDebugEnabled()) log.debug( "For Index file version " + indexFileVersion + ", reading an extra 16 bits for Entry " + entryIndex); short indexEntryExtendedFlags = dataBuffer.getShort(); entryBytesRead += 2; if (log.isDebugEnabled()) log.debug( "Entry " + entryIndex + " has (optional) extended flags " + indexEntryExtendedFlags); } String indexEntryName = null; if (indexFileVersion > 3) { if (log.isDebugEnabled()) log.debug( "Inflating the (deflated) entry name for index entry " + entryIndex + " based on the previous entry name, since Index file version " + indexFileVersion + " requires this"); // get bytes until we find one with the msb NOT set. count the bytes. int n = 0, removeNfromPreviousName = 0; byte msbsetmask = (byte) (1 << 7); // 1000 0000 byte msbunsetmask = (byte) ((~msbsetmask) & 0xFF); // 0111 1111 while (++n > 0) { byte byteRead = dataBuffer.get(); entryBytesRead++; if (n == 1) // zero the msb of the first byte read removeNfromPreviousName = (removeNfromPreviousName << 8) | (0xFF & (byteRead & msbunsetmask)); else // set the msb of subsequent bytes read removeNfromPreviousName = (removeNfromPreviousName << 8) | (0xFF & (byteRead | msbsetmask)); if ((byteRead & msbsetmask) == 0) break; // break if msb is NOT set in the byte } if (log.isDebugEnabled()) log.debug( "We read " + n + " bytes of variable length data from before the start of the entry name"); if (n > 4) throw new Exception( "An entry name is never expected to be > 2^^32 bytes long. Some file corruption may have occurred, or a parsing error has occurred"); // now read the (partial) name for the current entry int bytesToReadCurrentNameEntry = indexEntryNameByteLength - (previousIndexEntryName.length() - removeNfromPreviousName); byte[] indexEntryNameBuffer = new byte[bytesToReadCurrentNameEntry]; dataBuffer.get(indexEntryNameBuffer, 0, bytesToReadCurrentNameEntry); entryBytesRead += bytesToReadCurrentNameEntry; // build it up indexEntryName = previousIndexEntryName.substring( 0, previousIndexEntryName.length() - removeNfromPreviousName) + new String(indexEntryNameBuffer); } else { // indexFileVersion <= 3 (waaaaay simpler logic, but the index file is larger in this // version than for v4+) byte[] indexEntryNameBuffer = new byte[indexEntryNameByteLength]; dataBuffer.get(indexEntryNameBuffer, 0, indexEntryNameByteLength); entryBytesRead += indexEntryNameByteLength; indexEntryName = new String(indexEntryNameBuffer); } if (log.isDebugEnabled()) log.debug("Entry " + entryIndex + " has name " + indexEntryName); // and store off the index entry name, for the next iteration previousIndexEntryName = indexEntryName; // skip past the zero byte terminating the string (whose purpose seems completely // pointless to me, but hey) byte indexEntryNul = dataBuffer.get(); entryBytesRead++; // the padding after the pathname does not exist for versions 4 or later. if (indexFileVersion < 4) { if (log.isDebugEnabled()) log.debug( "Aligning to an 8 byte boundary after Entry " + entryIndex + ", since Index file version " + indexFileVersion + " mandates 64 bit alignment for index entries"); int entryBytesToRead = ((8 - (entryBytesRead % 8)) % 8); if (log.isDebugEnabled()) { log.debug( "The number of bytes read for index entry " + entryIndex + " thus far is: " + entryBytesRead); log.debug( "So we must read " + entryBytesToRead + " bytes to stay on a 64 bit boundary"); } // read the 0-7 (NUL) bytes to keep reading index entries on an 8 byte boundary byte[] indexEntryPadBuffer = new byte[entryBytesToRead]; dataBuffer.get(indexEntryPadBuffer, 0, entryBytesToRead); entryBytesRead += entryBytesToRead; } else { if (log.isDebugEnabled()) log.debug( "Not aligning to an 8 byte boundary after Entry " + entryIndex + ", since Index file version " + indexFileVersion + " does not mandate 64 bit alignment for index entries"); } // Git does not store entries for directories, but just files/symlinks/Git links, so no // need to handle directories here, unlike with SVN, for instance. if (indexEntryName != null && indexEntryName.length() > 0) { log.info( "Found file/symbolic link/gitlink " + indexEntryName + " in the Git entries file"); processURL(message, depth, "../" + indexEntryName, baseURL); } } // all good, we're outta here. // We consider the message fully parsed, so it doesn't get parsed by 'fallback' parsers return true; } else { throw new Exception( "The file '" + fullpath + "' could not be parsed as a Git Index file due to unexpected content"); } // end of Git HEAD handling logic } else { throw new Exception("This path cannot be handled by the Git parser"); } } catch (Exception e) { log.error("An error occurred trying to parse Git url '" + baseURL + "': " + e); // We consider the message fully parsed, so it doesn't get parsed by 'fallback' parsers return true; } }
@SuppressWarnings({"unchecked", "rawtypes"}) @Override public HttpMessage handleApiOther(HttpMessage msg, String name, JSONObject params) throws ApiException { if (OTHER_CHEETSHEET_ACTION_ORDER.equals(name) || OTHER_CHEETSHEET_KEY_ORDER.equals(name)) { List<KeyboardShortcut> shortcuts = this.extension.getShortcuts(); if (OTHER_CHEETSHEET_ACTION_ORDER.equals(name)) { Collections.sort( shortcuts, new Comparator() { @Override public int compare(Object o1, Object o2) { return ((KeyboardShortcut) o1) .getName() .compareTo(((KeyboardShortcut) o2).getName()); } }); } else { Collections.sort( shortcuts, new Comparator() { @Override public int compare(Object o1, Object o2) { return ((KeyboardShortcut) o1) .getKeyStrokeKeyCodeString() .compareTo(((KeyboardShortcut) o2).getKeyStrokeKeyCodeString()); } }); } StringBuilder response = new StringBuilder(); response.append(Constant.messages.getString("keyboard.api.cheatsheet.header")); boolean incUnset = this.getParam(params, PARAM_INC_UNSET, false); for (KeyboardShortcut shortcut : shortcuts) { if (incUnset || shortcut.getKeyStrokeKeyCodeString().length() > 0) { // Only show actions with actual shortcuts response.append( MessageFormat.format( Constant.messages.getString("keyboard.api.cheatsheet.tablerow"), shortcut.getName(), shortcut.getKeyStrokeModifiersString(), shortcut.getKeyStrokeKeyCodeString())); } } response.append(Constant.messages.getString("keyboard.api.cheatsheet.footer")); try { msg.setResponseHeader( "HTTP/1.1 200 OK\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n" + "Content-Length: " + response.length() + "\r\nContent-Type: text/html;"); } catch (HttpMalformedHeaderException e) { throw new ApiException(ApiException.Type.INTERNAL_ERROR, name, e); } msg.setResponseBody(response.toString()); return msg; } else { throw new ApiException(ApiException.Type.BAD_OTHER, name); } }
public RecordHistory getHistoryCache(HistoryReference ref, HttpMessage reqMsg) throws SQLException, HttpMalformedHeaderException { // get the cache from provided reference. // naturally, the obtained cache should be AFTER AND NEARBY to the given reference. // - historyId up to historyId+200 // - match sessionId // - history type can be MANUEL or hidden (hidden is used by images not explicitly stored in // history) // - match URI PreparedStatement psReadCache = null; if (isExistStatusCode) { // psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM HISTORY WHERE // URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= // ? AND SESSIONID = ? AND (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + // HistoryReference.TYPE_HIDDEN + ") AND STATUSCODE != 304"); psReadCache = getConnection() .prepareStatement( "SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND SESSIONID = ? AND STATUSCODE != 304"); } else { // psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE URI = // ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND // SESSIONID = ? AND (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + // HistoryReference.TYPE_HIDDEN + ")"); psReadCache = getConnection() .prepareStatement( "SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND " + HISTORYID + " >= ? AND " + HISTORYID + " <= ? AND SESSIONID = ?)"); } psReadCache.setString(1, reqMsg.getRequestHeader().getURI().toString()); psReadCache.setString(2, reqMsg.getRequestHeader().getMethod()); psReadCache.setBytes(3, reqMsg.getRequestBody().getBytes()); psReadCache.setInt(4, ref.getHistoryId()); psReadCache.setInt(5, ref.getHistoryId() + 200); psReadCache.setLong(6, ref.getSessionId()); ResultSet rs = psReadCache.executeQuery(); RecordHistory rec = null; try { do { rec = build(rs); // for retrieval from cache, the message requests nature must be the same. // and the result should NOT be NOT_MODIFIED for rendering by browser if (rec != null && rec.getHttpMessage().equals(reqMsg) && rec.getHttpMessage().getResponseHeader().getStatusCode() != HttpStatusCode.NOT_MODIFIED) { return rec; } } while (rec != null); } finally { try { rs.close(); psReadCache.close(); } catch (Exception e) { // ZAP: Log exceptions log.warn(e.getMessage(), e); } } // if cache not exist, probably due to NOT_MODIFIED, // lookup from cache BEFORE the given reference if (isExistStatusCode) { // psReadCache = getConnection().prepareStatement("SELECT TOP 1 * FROM HISTORY // WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND STATUSCODE != 304 AND // (HISTTYPE = " + HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + // HistoryReference.TYPE_HIDDEN + ")"); psReadCache = getConnection() .prepareStatement( "SELECT TOP 1 * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND STATUSCODE != 304"); } else { // psReadCache = getConnection().prepareStatement("SELECT * FROM HISTORY WHERE URI // = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ? AND (HISTTYPE = " + // HistoryReference.TYPE_MANUAL + " OR HISTTYPE = " + HistoryReference.TYPE_HIDDEN + ")"); psReadCache = getConnection() .prepareStatement( "SELECT * FROM HISTORY WHERE URI = ? AND METHOD = ? AND REQBODY = ? AND SESSIONID = ?"); } psReadCache.setString(1, reqMsg.getRequestHeader().getURI().toString()); psReadCache.setString(2, reqMsg.getRequestHeader().getMethod()); psReadCache.setBytes(3, reqMsg.getRequestBody().getBytes()); psReadCache.setLong(4, ref.getSessionId()); rs = psReadCache.executeQuery(); rec = null; try { do { rec = build(rs); if (rec != null && rec.getHttpMessage().equals(reqMsg) && rec.getHttpMessage().getResponseHeader().getStatusCode() != HttpStatusCode.NOT_MODIFIED) { return rec; } } while (rec != null); } finally { try { rs.close(); psReadCache.close(); } catch (Exception e) { // ZAP: Log exceptions log.warn(e.getMessage(), e); } } return null; }
public synchronized RecordHistory write(long sessionId, int histType, HttpMessage msg) throws HttpMalformedHeaderException, SQLException { String reqHeader = ""; byte[] reqBody = new byte[0]; String resHeader = ""; byte[] resBody = reqBody; String method = ""; String uri = ""; int statusCode = 0; if (!msg.getRequestHeader().isEmpty()) { reqHeader = msg.getRequestHeader().toString(); reqBody = msg.getRequestBody().getBytes(); method = msg.getRequestHeader().getMethod(); uri = msg.getRequestHeader().getURI().toString(); } if (!msg.getResponseHeader().isEmpty()) { resHeader = msg.getResponseHeader().toString(); resBody = msg.getResponseBody().getBytes(); statusCode = msg.getResponseHeader().getStatusCode(); } // return write(sessionId, histType, msg.getTimeSentMillis(), msg.getTimeElapsedMillis(), // method, uri, statusCode, reqHeader, reqBody, resHeader, resBody, msg.getTag()); return write( sessionId, histType, msg.getTimeSentMillis(), msg.getTimeElapsedMillis(), method, uri, statusCode, reqHeader, reqBody, resHeader, resBody, null); }