@@ -13,6 +13,8 @@ import (
13
13
"strings"
14
14
"sync"
15
15
16
+ "maps"
17
+
16
18
"github.com/panjf2000/ants/v2"
17
19
"github.com/wailsapp/wails/v2/pkg/runtime"
18
20
)
@@ -84,6 +86,7 @@ func ExtractJS(ctx context.Context, url string) (allJS []string) {
84
86
content := string (body )
85
87
for _ , reg := range regJS {
86
88
for _ , item := range reg .FindAllString (content , - 1 ) {
89
+ item = strings .TrimSpace (item )
87
90
item = strings .TrimLeft (item , "=" )
88
91
item = strings .Trim (item , "\" " )
89
92
item = strings .Trim (item , "'" )
@@ -261,8 +264,8 @@ func FilterExt(iss []structs.InfoSource) (news []structs.InfoSource) {
261
264
// 处理 API 逻辑
262
265
func AnalyzeAPI (ctx context.Context , o structs.JSFindOptions ) {
263
266
resp , err := clients .SimpleGet (o .HomeURL , clients .NewRestyClient (nil , true ))
264
- if err != nil || resp == nil {
265
- gologger .Error (ctx , fmt .Sprintf ("[AnalyzeAPI] %s, error : %v" , o .HomeURL , err ))
267
+ if err != nil {
268
+ gologger .Error (ctx , fmt .Sprintf ("[AnalyzeAPI] 请求首页失败 %s, 错误 : %v" , o .HomeURL , err ))
266
269
return
267
270
}
268
271
homeBody := string (resp .Body ())
@@ -272,59 +275,127 @@ func AnalyzeAPI(ctx context.Context, o structs.JSFindOptions) {
272
275
defer wg .Done ()
273
276
api := data .(string )
274
277
278
+ // 为每个 API 独立生成 fullURL 和 headers 副本
275
279
fullURL := strings .TrimRight (o .BaseURL , "/" ) + "/" + strings .TrimLeft (api , "/" )
276
- method , err := detectMethod (fullURL , o .Headers )
280
+
281
+ // 拷贝 headers,避免竞争
282
+ apiHeaders := make (map [string ]string )
283
+ maps .Copy (apiHeaders , o .Headers )
284
+
285
+ // 检测请求方法
286
+ method , err := detectMethod (fullURL , apiHeaders )
277
287
if err != nil {
278
- runtime .EventsEmit (ctx , "jsfindlog" , fmt .Sprintf ("[!] %s : %v" , fullURL , err ))
288
+ runtime .EventsEmit (ctx , "jsfindlog" , fmt .Sprintf ("[!] %s 检测请求方法失败 : %v" , fullURL , err ))
279
289
return
280
290
}
281
291
292
+ // 如果是 POST 方法,动态探测 Content-Type
282
293
if method == http .MethodPost {
283
- contentType := detectContentType (fullURL , o .Headers )
284
- if contentType != "" {
285
- o .Headers ["Content-Type" ] = contentType
294
+ if contentType := detectContentType (fullURL , apiHeaders ); contentType != "" {
295
+ apiHeaders ["Content-Type" ] = contentType
286
296
}
287
297
}
288
298
299
+ // 补全参数
289
300
param := completeParameters (method , fullURL , url.Values {})
290
301
if param != nil {
291
- runtime .EventsEmit (ctx , "jsfindlog" , fmt .Sprintf ("[+] %s 已补全参数 %s" , fullURL , param .Encode ()))
302
+ runtime .EventsEmit (ctx , "jsfindlog" , fmt .Sprintf ("[+] %s 已补全参数: %s" , fullURL , param .Encode ()))
292
303
}
293
304
305
+ // 构建请求对象
294
306
apiReq := APIRequest {
295
307
URL : fullURL ,
296
308
Method : method ,
297
- Headers : o . Headers ,
309
+ Headers : apiHeaders ,
298
310
Params : param ,
299
311
}
312
+
313
+ // 检查高风险路由,直接跳过测试
300
314
for _ , router := range o .HighRiskRouter {
301
315
if strings .Contains (apiReq .URL , router ) {
302
- runtime .EventsEmit (ctx , "jsfindlog" , "[!!] " + fullURL + " 高风险API已跳过测试, 相关敏感词 : " + router )
316
+ runtime .EventsEmit (ctx , "jsfindlog" , "[!!] " + fullURL + " 高风险API跳过测试, 触发敏感词 : " + router )
303
317
return
304
318
}
305
319
}
320
+
306
321
// 测试未授权访问
307
322
vulnerable , body , err := testUnauthorizedAccess (homeBody , apiReq , o .Authentication )
308
- if err != nil || ! vulnerable {
309
- runtime .EventsEmit (ctx , "jsfindlog" , "[-] " + fullURL + " 不存在未授权" )
323
+ if err != nil {
324
+ runtime .EventsEmit (ctx , "jsfindlog" , fmt .Sprintf ("[-] %s 测试未授权错误: %v" , fullURL , err ))
325
+ return
326
+ }
327
+
328
+ if ! vulnerable {
329
+ runtime .EventsEmit (ctx , "jsfindlog" , "[-] " + fullURL + " 不存在未授权访问" )
310
330
return
311
331
}
312
- runtime .EventsEmit (ctx , "jsfindlog" , "[+] " + fullURL + " 存在未授权" )
332
+
333
+ // 存在未授权,记录漏洞信息
334
+ runtime .EventsEmit (ctx , "jsfindlog" , "[+] " + fullURL + " 存在未授权访问!" )
313
335
runtime .EventsEmit (ctx , "jsfindvulcheck" , structs.JSFindResult {
314
336
Method : method ,
315
- Param : param . Encode ( ),
337
+ Request : buildRawRequest ( apiReq ),
316
338
Source : fullURL ,
317
339
Response : string (body ),
318
340
Length : len (body ),
319
341
})
320
342
})
321
343
defer pool .Release ()
322
344
323
- // 提交 API 任务到线程池
345
+ // 提交任务
324
346
for _ , api := range o .ApiList {
325
347
wg .Add (1 )
326
348
pool .Invoke (api )
327
349
}
328
350
329
351
wg .Wait ()
330
352
}
353
+
354
+ func buildRawRequest (req APIRequest ) string {
355
+ var sb strings.Builder
356
+
357
+ parsedURL , err := url .Parse (req .URL )
358
+ if err != nil {
359
+ return "" // URL解析失败直接返回空
360
+ }
361
+
362
+ // 如果是 GET 请求,需要将参数附加到 URL 后面
363
+ if req .Method == http .MethodGet && req .Params != nil {
364
+ // 编码查询参数并将其附加到 URL
365
+ queryString := req .Params .Encode ()
366
+ if parsedURL .RawQuery == "" {
367
+ parsedURL .RawQuery = queryString
368
+ } else {
369
+ parsedURL .RawQuery += "&" + queryString
370
+ }
371
+ }
372
+
373
+ // 写入请求行,只保留Path+Query,不要域名
374
+ pathWithQuery := parsedURL .Path
375
+ if parsedURL .RawQuery != "" {
376
+ pathWithQuery += "?" + parsedURL .RawQuery
377
+ }
378
+ sb .WriteString (fmt .Sprintf ("%s %s HTTP/1.1\n " , req .Method , pathWithQuery ))
379
+
380
+ // Host 头
381
+ sb .WriteString (fmt .Sprintf ("Host: %s\n " , parsedURL .Host ))
382
+
383
+ // 其他 Headers
384
+ for k , v := range req .Headers {
385
+ // 避免重复写 Host
386
+ if strings .ToLower (k ) == "host" {
387
+ continue
388
+ }
389
+ sb .WriteString (fmt .Sprintf ("%s: %s\n " , k , v ))
390
+ }
391
+
392
+ sb .WriteString ("\n " ) // 头结束空一行
393
+
394
+ // 写入请求体
395
+ if req .Method != http .MethodGet && req .Params != nil {
396
+ // 如果是非 GET 请求,参数放到请求体中
397
+ sb .WriteString (req .Params .Encode ())
398
+ }
399
+
400
+ return sb .String ()
401
+ }
0 commit comments