11package Main ;
22
3+ import Main .ghostbits .AutoGhostBitsGenerator ;
4+ import Main .ghostbits .GhostBitsAutoVariant ;
5+ import Main .ghostbits .RawSocketSender ;
36import burp .*;
47import org .apache .commons .lang3 .StringUtils ;
58
@@ -317,7 +320,8 @@ public void run() {
317320
318321 if (finalResponse != null && finalResponseBytes != null && shouldLog ) {
319322 String title = Utils .getBodyTitle (new String (finalResponseBytes , "utf-8" ));
320- addLog (finalResponse , 0 , 0 , 0 , title , tool );
323+ String reason = buildAutoReason (oldStatus , newStatus , ratio , threshold , statusClassChanged );
324+ addLog (finalResponse , title , tool , reason );
321325 }
322326
323327 } catch (Throwable ee ) {
@@ -333,6 +337,45 @@ private static boolean isCandidateStatus(short statusCode) {
333337 || statusCode == 405 || statusCode == 415 ;
334338 }
335339
340+ /**
341+ * Dashboard only persists addLog's 200/405/415 subset; this reason explains
342+ * the already-accepted candidate without changing that existing filter.
343+ */
344+ static String buildAutoReason (short oldStatus , short newStatus ,
345+ double ratio , double threshold ,
346+ boolean statusClassChanged ) {
347+ StringBuilder sb = new StringBuilder ();
348+ boolean hasStatus = oldStatus > 0 || newStatus > 0 ;
349+ if (hasStatus ) {
350+ sb .append ("status:" )
351+ .append (formatStatus (oldStatus ))
352+ .append (" -> " )
353+ .append (formatStatus (newStatus ));
354+ }
355+
356+ boolean ratioUsable = Double .isFinite (ratio ) && Double .isFinite (threshold );
357+ if (ratioUsable && ratio < threshold ) {
358+ if (sb .length () > 0 ) {
359+ sb .append ("; " );
360+ }
361+ sb .append ("sim:" )
362+ .append (String .format (Locale .ROOT , "%.2f" , ratio ))
363+ .append (" < " )
364+ .append (String .format (Locale .ROOT , "%.2f" , threshold ));
365+ } else if (statusClassChanged ) {
366+ if (sb .length () > 0 ) {
367+ sb .append ("; " );
368+ }
369+ sb .append ("class changed" );
370+ }
371+
372+ return sb .toString ();
373+ }
374+
375+ private static String formatStatus (short status ) {
376+ return status <= 0 ? "?" : Short .toString (status );
377+ }
378+
336379 private static boolean isRedirectStatus (short statusCode ) {
337380 return statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 307 || statusCode == 308 ;
338381 }
@@ -523,7 +566,7 @@ public void actionPerformed(ActionEvent e) {
523566
524567 new Thread (() -> {
525568 IHttpRequestResponse [] iHttpRequestResponses = invocation .getSelectedMessages ();
526- processHttp (iHttpRequestResponses , "Auth Bypass (Auto) " , "access_control" );
569+ processHttp (iHttpRequestResponses , "send access " , Utils . PROFILE_AUTO_ACCESS_BYPASS );
527570 }).start ();
528571
529572 }
@@ -533,8 +576,8 @@ public void actionPerformed(ActionEvent e) {
533576 public void actionPerformed (ActionEvent e ) {
534577 new Thread (() -> {
535578 IHttpRequestResponse [] iHttpRequestResponses = invocation .getSelectedMessages ();
536- // WAF 模式:若配置未提供 waf profile,会回退到 access_control
537- processHttp (iHttpRequestResponses , "WAF Bypass (Auto) " , "waf" );
579+ // WAF 模式:使用 profiles.auto_waf_bypass
580+ processHttp (iHttpRequestResponses , "send waf " , Utils . PROFILE_AUTO_WAF_BYPASS );
538581 }).start ();
539582 }
540583 });
@@ -558,7 +601,7 @@ public void actionPerformed(ActionEvent e) {
558601 return list ;
559602 }
560603
561- private void addLog (IHttpRequestResponse messageInfo , int toolFlag , long time , int row , String title , String tool ) {
604+ private void addLog (IHttpRequestResponse messageInfo , String title , String tool , String reason ) {
562605 // 入表写 UI:放到 EDT 串行执行,避免并发写 ArrayList 造成数据竞争
563606 try {
564607 short statusCode = Utils .helpers .analyzeResponse (messageInfo .getResponse ()).getStatusCode ();
@@ -572,7 +615,7 @@ private void addLog(IHttpRequestResponse messageInfo, int toolFlag, long time, i
572615 Utils .helpers .analyzeResponse (messageInfo .getResponse ()).getStatusCode (),
573616 Utils .helpers .analyzeResponse (messageInfo .getResponse ()).getStatedMimeType (),
574617 title ,
575- nextId (), tool ));
618+ nextId (), tool , reason ));
576619 }
577620 } catch (Exception ignored ) {
578621 }
@@ -641,7 +684,7 @@ public void processHttp(IHttpRequestResponse[] iHttpRequestResponses, String too
641684 }
642685
643686 // WAF 模式:额外生成 Body 编码变体(仅 POST/PUT/PATCH)
644- if ("waf" .equals (profile )) {
687+ if (Utils . PROFILE_AUTO_WAF_BYPASS .equals (profile )) {
645688 byte [] requestBytes = iHttpRequestResponse .getRequest ();
646689 if (requestBytes != null ) {
647690 IRequestInfo wafReqInfo = Utils .helpers .analyzeRequest (iHttpRequestResponse .getHttpService (),
@@ -657,6 +700,12 @@ public void processHttp(IHttpRequestResponse[] iHttpRequestResponses, String too
657700 es .submit (new Run_body_encoded_request (encodedRequest , iHttpRequestResponse , tool ));
658701 }
659702 }
703+
704+ List <GhostBitsAutoVariant > ghostVariants = generateGhostBitsAutoVariants (requestBytes );
705+ addAllRequestNum (ghostVariants .size ());
706+ for (GhostBitsAutoVariant variant : ghostVariants ) {
707+ es .submit (new Run_ghost_bits_request (variant , iHttpRequestResponse , tool ));
708+ }
660709 }
661710 }
662711
@@ -665,6 +714,13 @@ public void processHttp(IHttpRequestResponse[] iHttpRequestResponses, String too
665714
666715 }
667716
717+ private List <GhostBitsAutoVariant > generateGhostBitsAutoVariants (byte [] requestBytes ) {
718+ AutoGhostBitsGenerator generator = new AutoGhostBitsGenerator (
719+ Utils .getGhostBitsRule (),
720+ Utils .getWafGhostBitsOptions ());
721+ return generator .generate (requestBytes );
722+ }
723+
668724 /**
669725 * 判断请求是否有 Body
670726 */
@@ -1140,16 +1196,169 @@ public void run() {
11401196
11411197 if (finalResponse != null && finalResponseBytes != null && shouldLog ) {
11421198 String title = Utils .getBodyTitle (new String (finalResponseBytes , "utf-8" ));
1143- addLog (finalResponse , 0 , 0 , 0 , title , tool );
1199+ String reason = buildAutoReason (oldStatus , newStatus , ratio , threshold , statusClassChanged );
1200+ addLog (finalResponse , title , tool , reason );
11441201 }
11451202 } catch (Throwable e ) {
11461203 Utils .panel .addErrorRequestNum (1 );
11471204 }
11481205 }
11491206 }
11501207
1208+ private String ghostToolLabel (String tool ) {
1209+ if (tool == null || tool .trim ().isEmpty ()) {
1210+ return "ghost" ;
1211+ }
1212+ String normalized = tool .trim ().toLowerCase ();
1213+ if ("auto" .equals (normalized )) {
1214+ return "auto-waf/ghost" ;
1215+ }
1216+ if ("send waf" .equals (normalized )) {
1217+ return "send-waf/ghost" ;
1218+ }
1219+ return tool + "/ghost" ;
1220+ }
1221+
1222+ /**
1223+ * Auto WAF 的 Ghost Bits 模板探测请求。
1224+ * 关键区别:path/header 含非 ASCII 或模板 sender=raw 时走 RawSocketSender。
1225+ */
1226+ class Run_ghost_bits_request implements Runnable {
1227+ private final GhostBitsAutoVariant variant ;
1228+ private final IHttpRequestResponse originalReqResp ;
1229+ private final String tool ;
1230+
1231+ public Run_ghost_bits_request (GhostBitsAutoVariant variant , IHttpRequestResponse originalReqResp , String tool ) {
1232+ this .variant = variant ;
1233+ this .originalReqResp = originalReqResp ;
1234+ this .tool = tool ;
1235+ }
1236+
1237+ @ Override
1238+ public void run () {
1239+ try {
1240+ IHttpService service = originalReqResp .getHttpService ();
1241+ IHttpRequestResponse finalResponse ;
1242+ if (variant .isRawRequired ()) {
1243+ finalResponse = sendGhostVariantRaw (variant , service );
1244+ } else {
1245+ IHttpRequestResponse firstResponse = Utils .callbacks .makeHttpRequest (service , variant .getRequestBytes ());
1246+ finalResponse = followRedirectsIfNeeded (firstResponse , variant .getRequestBytes (),
1247+ service , MAX_REDIRECT_HOPS );
1248+ }
1249+
1250+ byte [] finalResponseBytes = finalResponse == null ? null : finalResponse .getResponse ();
1251+ byte [] oldResponseBytes = originalReqResp .getResponse ();
1252+ short oldStatus = getStatusSafe (oldResponseBytes );
1253+ short newStatus = getStatusSafe (finalResponseBytes );
1254+ String newMime = getMimeSafe (finalResponseBytes );
1255+
1256+ String oldBody = extractBodyAsString (oldResponseBytes );
1257+ String newBody = extractBodyAsString (finalResponseBytes );
1258+ double threshold = Utils .panel .getSimilarityThreshold ();
1259+ double ratio = DiffPage .getRatio (oldBody , newBody , newMime );
1260+ boolean statusClassChanged = (oldStatus > 0 && newStatus > 0 ) && (oldStatus / 100 != newStatus / 100 );
1261+
1262+ String baseReason = buildAutoReason (oldStatus , newStatus , ratio , threshold , statusClassChanged );
1263+ String signatureReason = buildGhostSignatureReason (newBody );
1264+ boolean signatureMatched = !signatureReason .isEmpty ();
1265+ boolean shouldLog = isCandidateStatus (newStatus )
1266+ && (signatureMatched || ratio < threshold || statusClassChanged );
1267+ String reason = variant .getReason ();
1268+ if (!baseReason .isEmpty ()) {
1269+ reason += "; " + baseReason ;
1270+ }
1271+ if (signatureMatched ) {
1272+ reason += "; " + signatureReason ;
1273+ }
1274+
1275+ addFinishRequestNum (1 );
1276+
1277+ if (finalResponse != null && finalResponseBytes != null && shouldLog ) {
1278+ String title = Utils .getBodyTitle (new String (finalResponseBytes , "utf-8" ));
1279+ addLog (finalResponse , title , ghostToolLabel (tool ), reason );
1280+ }
1281+ } catch (Throwable e ) {
1282+ Utils .panel .addErrorRequestNum (1 );
1283+ }
1284+ }
1285+ }
1286+
1287+ private IHttpRequestResponse sendGhostVariantRaw (GhostBitsAutoVariant variant , IHttpService service ) throws Exception {
1288+ String host = service .getHost ();
1289+ int port = service .getPort ();
1290+ boolean https = "https" .equalsIgnoreCase (service .getProtocol ());
1291+ if (port <= 0 ) {
1292+ port = https ? 443 : 80 ;
1293+ }
1294+ RawSocketSender .RawResponse raw = new RawSocketSender ().send (host , port , https ,
1295+ variant .getRequestBytes (), 5000 , 5000 );
1296+ return new SimpleHttpRequestResponse (service ,
1297+ raw .getRequestBytesActuallySent (),
1298+ raw .getResponseBytes ());
1299+ }
1300+
1301+ private String buildGhostSignatureReason (String body ) {
1302+ if (body == null || body .isEmpty ()) {
1303+ return "" ;
1304+ }
1305+ String lower = body .toLowerCase (Locale .ROOT );
1306+ if (lower .contains ("root:x:" ) || lower .contains ("/bin/bash" )
1307+ || lower .contains ("nologin" ) || lower .contains ("daemon:x:" )) {
1308+ return "ghost file signature matched" ;
1309+ }
1310+ if (lower .contains ("[fonts]" ) || lower .contains ("[extensions]" ) || lower .contains ("[mci extensions]" )) {
1311+ return "ghost file signature matched" ;
1312+ }
1313+ return "" ;
1314+ }
1315+
1316+ static class SimpleHttpRequestResponse implements IHttpRequestResponse {
1317+ private byte [] request ;
1318+ private byte [] response ;
1319+ private String comment ;
1320+ private String highlight ;
1321+ private IHttpService service ;
1322+
1323+ SimpleHttpRequestResponse (IHttpService service , byte [] request , byte [] response ) {
1324+ this .service = service ;
1325+ this .request = request == null ? new byte [0 ] : request ;
1326+ this .response = response == null ? new byte [0 ] : response ;
1327+ }
1328+
1329+ @ Override
1330+ public byte [] getRequest () { return request ; }
1331+
1332+ @ Override
1333+ public void setRequest (byte [] message ) { request = message == null ? new byte [0 ] : message ; }
1334+
1335+ @ Override
1336+ public byte [] getResponse () { return response ; }
1337+
1338+ @ Override
1339+ public void setResponse (byte [] message ) { response = message == null ? new byte [0 ] : message ; }
1340+
1341+ @ Override
1342+ public String getComment () { return comment ; }
1343+
1344+ @ Override
1345+ public void setComment (String comment ) { this .comment = comment ; }
1346+
1347+ @ Override
1348+ public String getHighlight () { return highlight ; }
1349+
1350+ @ Override
1351+ public void setHighlight (String color ) { this .highlight = color ; }
1352+
1353+ @ Override
1354+ public IHttpService getHttpService () { return service ; }
1355+
1356+ @ Override
1357+ public void setHttpService (IHttpService httpService ) { this .service = httpService ; }
1358+ }
1359+
11511360 public void processHttp (IHttpRequestResponse [] iHttpRequestResponses , String tool ) {
1152- processHttp (iHttpRequestResponses , tool , "access_control" );
1361+ processHttp (iHttpRequestResponses , tool , Utils . PROFILE_AUTO_ACCESS_BYPASS );
11531362 }
11541363
11551364 /**
@@ -1177,7 +1386,7 @@ public void processProxyMessage(boolean messageIsRequest, IInterceptedProxyMessa
11771386 }
11781387 }
11791388 new Thread (() -> {
1180- processHttp (iHttpRequestResponses , "Auto Scan " , "access_control" );
1389+ processHttp (iHttpRequestResponses , "auto " , Utils . PROFILE_AUTO_ACCESS_BYPASS );
11811390 }).start ();
11821391
11831392 }
0 commit comments