Skip to content

Commit a1f5e96

Browse files
author
wuerror
committed
Implement: Phase 8.3 Smart PoC - Enhanced ApiRoute with Metadata & Generated HTTP Payloads
1 parent 747be93 commit a1f5e96

5 files changed

Lines changed: 548 additions & 7 deletions

File tree

src/main/java/com/jbytescanner/engine/RouteExtractor.java

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
import com.jbytescanner.model.ApiRoute;
44
import soot.*;
55
import soot.tagkit.AnnotationTag;
6+
import soot.tagkit.VisibilityAnnotationTag;
7+
import soot.tagkit.VisibilityParameterAnnotationTag;
68
import org.slf4j.Logger;
79
import org.slf4j.LoggerFactory;
810

911
import java.util.ArrayList;
1012
import java.util.Arrays;
1113
import java.util.List;
14+
import java.util.Map;
15+
import java.util.HashMap;
1216

1317
public class RouteExtractor {
1418
private static final Logger logger = LoggerFactory.getLogger(RouteExtractor.class);
@@ -21,6 +25,11 @@ public class RouteExtractor {
2125
private static final String ANN_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping";
2226
private static final String ANN_PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping";
2327
private static final String ANN_DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping";
28+
29+
// Param Annotations
30+
private static final String ANN_REQUEST_BODY = "org.springframework.web.bind.annotation.RequestBody";
31+
private static final String ANN_REQUEST_PARAM = "org.springframework.web.bind.annotation.RequestParam";
32+
private static final String ANN_PATH_VARIABLE = "org.springframework.web.bind.annotation.PathVariable";
2433

2534
// Servlet
2635
private static final String CLASS_HTTP_SERVLET = "javax.servlet.http.HttpServlet";
@@ -135,8 +144,15 @@ private List<ApiRoute> extractSpringRoutes(SootClass sc) {
135144
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "value"));
136145
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "path"));
137146
// Attempt to extract method from RequestMethod enum is complex in bytecode,
138-
// often defaults to ALL if not easily statically resolved
139-
httpMethod = "ALL";
147+
// often defaults to ALL if not easily statically resolved.
148+
// For PoC, we default to GET if unknown, or try to parse 'method' attribute.
149+
List<String> methods = AnnotationHelper.getAnnotationValues(tag, "method");
150+
if (!methods.isEmpty()) {
151+
httpMethod = methods.get(0).replace("RequestMethod.", "");
152+
} else {
153+
httpMethod = "ALL";
154+
}
155+
140156
} else if (AnnotationHelper.hasAnnotation(sm, ANN_GET_MAPPING)) {
141157
AnnotationTag tag = AnnotationHelper.getAnnotation(sm, ANN_GET_MAPPING);
142158
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "value"));
@@ -147,16 +163,33 @@ private List<ApiRoute> extractSpringRoutes(SootClass sc) {
147163
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "value"));
148164
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "path"));
149165
httpMethod = "POST";
166+
} else if (AnnotationHelper.hasAnnotation(sm, ANN_PUT_MAPPING)) {
167+
AnnotationTag tag = AnnotationHelper.getAnnotation(sm, ANN_PUT_MAPPING);
168+
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "value"));
169+
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "path"));
170+
httpMethod = "PUT";
171+
} else if (AnnotationHelper.hasAnnotation(sm, ANN_DELETE_MAPPING)) {
172+
AnnotationTag tag = AnnotationHelper.getAnnotation(sm, ANN_DELETE_MAPPING);
173+
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "value"));
174+
methodPaths.addAll(AnnotationHelper.getAnnotationValues(tag, "path"));
175+
httpMethod = "DELETE";
150176
}
151-
// Add PUT, DELETE similarly...
152177

153178
if (httpMethod != null) {
154179
if (methodPaths.isEmpty()) methodPaths.add("");
155180

181+
// Extract Parameters Info
182+
RouteMetadata metadata = extractRouteMetadata(sm);
183+
156184
for (String cp : classPaths) {
157185
for (String mp : methodPaths) {
158186
String fullPath = combinePaths(cp, mp);
159-
routes.add(new ApiRoute(httpMethod, fullPath, sc.getName(), sm.getSubSignature()));
187+
188+
ApiRoute route = new ApiRoute(
189+
httpMethod, fullPath, sc.getName(), sm.getSubSignature(),
190+
metadata.parameters, metadata.paramAnnotations, metadata.contentType
191+
);
192+
routes.add(route);
160193
}
161194
}
162195
}
@@ -168,6 +201,71 @@ private String combinePaths(String p1, String p2) {
168201
if (!p1.startsWith("/")) p1 = "/" + p1;
169202
if (!p2.startsWith("/") && !p2.isEmpty()) p2 = "/" + p2;
170203
if (p1.endsWith("/") && p2.startsWith("/")) return p1 + p2.substring(1);
204+
if (p1.equals("/") && p2.startsWith("/")) return p2; // Avoid //api
171205
return p1 + p2;
172206
}
207+
208+
// --- Phase 8.3 Metadata Extraction ---
209+
210+
private static class RouteMetadata {
211+
List<String> parameters = new ArrayList<>();
212+
Map<String, String> paramAnnotations = new HashMap<>();
213+
String contentType = "application/x-www-form-urlencoded"; // Default
214+
}
215+
216+
private RouteMetadata extractRouteMetadata(SootMethod sm) {
217+
RouteMetadata meta = new RouteMetadata();
218+
219+
// 1. Basic Parameter Info (Name:Type)
220+
// Try to get names from ActiveBody (LocalVariableTable) if available
221+
List<String> paramNames = new ArrayList<>();
222+
try {
223+
if (sm.hasActiveBody()) {
224+
// Not reliable for interfaces/abstract, but Controllers usually have bodies
225+
// However, without debug info (-g:vars), names are arg0, arg1...
226+
// Spring uses -parameters flag usually.
227+
// We rely on simple counting for now: arg0...
228+
}
229+
} catch (Exception e) {}
230+
231+
// 2. Annotation Analysis (VisibilityParameterAnnotationTag)
232+
// Format: Tag -> Annotations[] -> Annotation
233+
VisibilityParameterAnnotationTag tag = (VisibilityParameterAnnotationTag) sm.getTag("VisibilityParameterAnnotationTag");
234+
235+
int paramCount = sm.getParameterCount();
236+
for (int i = 0; i < paramCount; i++) {
237+
String name = "arg" + i;
238+
Type type = sm.getParameterType(i);
239+
meta.parameters.add(name + ":" + type.toString());
240+
241+
// Check for Multipart
242+
if (type.toString().contains("MultipartFile")) {
243+
meta.contentType = "multipart/form-data";
244+
}
245+
246+
if (tag != null && tag.getVisibilityAnnotations() != null && i < tag.getVisibilityAnnotations().size()) {
247+
VisibilityAnnotationTag paramTags = tag.getVisibilityAnnotations().get(i);
248+
if (paramTags != null && paramTags.getAnnotations() != null) {
249+
for (AnnotationTag at : paramTags.getAnnotations()) {
250+
String typeName = at.getType().replace("/", ".").replace(";", "");
251+
if (typeName.startsWith("L")) typeName = typeName.substring(1); // Remove L prefix
252+
253+
if (typeName.equals(ANN_REQUEST_BODY)) {
254+
meta.paramAnnotations.put(name, "RequestBody");
255+
meta.contentType = "application/json";
256+
} else if (typeName.equals(ANN_REQUEST_PARAM)) {
257+
meta.paramAnnotations.put(name, "RequestParam");
258+
// If we have @RequestParam("alias"), we should extract it.
259+
// But parsing annotation values here is complex (requires searching elems).
260+
// For MVP, we stick to argX or rely on parameter name preservation.
261+
} else if (typeName.equals(ANN_PATH_VARIABLE)) {
262+
meta.paramAnnotations.put(name, "PathVariable");
263+
}
264+
}
265+
}
266+
}
267+
}
268+
269+
return meta;
270+
}
173271
}

src/main/java/com/jbytescanner/engine/TaintEngine.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public void run() {
127127
if (!vulnerabilities.isEmpty()) {
128128
SarifReporter reporter = new SarifReporter(workspaceDir);
129129
reporter.generate(vulnerabilities);
130+
131+
// Phase 8.3: Generate Smart PoCs
132+
logger.info("Generating Smart PoC payloads...");
133+
com.jbytescanner.report.PoCReporter pocReporter = new com.jbytescanner.report.PoCReporter(workspaceDir);
134+
pocReporter.generate(vulnerabilities, routes);
130135
} else {
131136
logger.info("No vulnerabilities found. Skipping report generation.");
132137
}
@@ -180,9 +185,47 @@ private List<ApiRoute> loadEntryPoints() {
180185
List<String> lines = Files.readAllLines(apiFile.toPath());
181186
for (String line : lines) {
182187
if (line.startsWith("#") || line.trim().isEmpty()) continue;
183-
String[] parts = line.split(" ", 4);
188+
189+
String metaJson = null;
190+
String baseLine = line;
191+
192+
// Check for metadata separator
193+
if (line.contains(" | {")) {
194+
int splitIdx = line.indexOf(" | {");
195+
baseLine = line.substring(0, splitIdx);
196+
metaJson = line.substring(splitIdx + 3); // "{...}"
197+
}
198+
199+
String[] parts = baseLine.split(" ", 4);
184200
if (parts.length >= 4) {
185-
routes.add(new ApiRoute(parts[0], parts[1], parts[2], parts[3]));
201+
ApiRoute route = new ApiRoute(parts[0], parts[1], parts[2], parts[3]);
202+
203+
if (metaJson != null) {
204+
try {
205+
com.google.gson.JsonObject json = com.google.gson.JsonParser.parseString(metaJson).getAsJsonObject();
206+
207+
if (json.has("contentType")) {
208+
route.setContentType(json.get("contentType").getAsString());
209+
}
210+
211+
if (json.has("params")) {
212+
List<String> params = new ArrayList<>();
213+
com.google.gson.JsonArray arr = json.getAsJsonArray("params");
214+
arr.forEach(e -> params.add(e.getAsString()));
215+
route.setParameters(params);
216+
}
217+
218+
if (json.has("annotations")) {
219+
java.util.Map<String, String> anns = new java.util.HashMap<>();
220+
com.google.gson.JsonObject obj = json.getAsJsonObject("annotations");
221+
obj.entrySet().forEach(e -> anns.put(e.getKey(), e.getValue().getAsString()));
222+
route.setParamAnnotations(anns);
223+
}
224+
} catch (Exception e) {
225+
logger.warn("Failed to parse metadata for route: {}", parts[1]);
226+
}
227+
}
228+
routes.add(route);
186229
}
187230
}
188231
} catch (IOException e) {

src/main/java/com/jbytescanner/model/ApiRoute.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import lombok.AllArgsConstructor;
44
import lombok.Data;
55
import lombok.NoArgsConstructor;
6+
import java.util.List;
7+
import java.util.Map;
68

79
@Data
810
@AllArgsConstructor
@@ -12,13 +14,66 @@ public class ApiRoute {
1214
private String path; // /api/v1/user
1315
private String className; // com.example.UserController
1416
private String methodSig; // java.lang.String getUser(java.lang.String)
17+
18+
// Phase 8.3: Metadata for PoC Generation
19+
private List<String> parameters; // Format: "name:type"
20+
private Map<String, String> paramAnnotations; // Format: "paramName" -> "AnnotationType" (e.g., "user" -> "RequestBody")
21+
private String contentType; // e.g., "application/json", "application/x-www-form-urlencoded"
22+
23+
// Legacy Constructor for compatibility
24+
public ApiRoute(String httpMethod, String path, String className, String methodSig) {
25+
this.httpMethod = httpMethod;
26+
this.path = path;
27+
this.className = className;
28+
this.methodSig = methodSig;
29+
}
1530

1631
@Override
1732
public String toString() {
18-
return String.format("%s %s %s %s",
33+
String base = String.format("%s %s %s %s",
1934
httpMethod != null ? httpMethod : "ALL",
2035
path,
2136
className,
2237
methodSig);
38+
39+
// Phase 8.3: Append Metadata in JSON format for persistence
40+
if (contentType != null || (parameters != null && !parameters.isEmpty())) {
41+
StringBuilder json = new StringBuilder();
42+
json.append(" | {");
43+
boolean hasPrev = false;
44+
45+
if (contentType != null) {
46+
json.append("\"contentType\":\"").append(contentType).append("\"");
47+
hasPrev = true;
48+
}
49+
50+
if (parameters != null && !parameters.isEmpty()) {
51+
if (hasPrev) json.append(", ");
52+
json.append("\"params\":[");
53+
for (int i = 0; i < parameters.size(); i++) {
54+
if (i > 0) json.append(",");
55+
json.append("\"").append(parameters.get(i)).append("\"");
56+
}
57+
json.append("]");
58+
hasPrev = true;
59+
}
60+
61+
if (paramAnnotations != null && !paramAnnotations.isEmpty()) {
62+
if (hasPrev) json.append(", ");
63+
json.append("\"annotations\":{");
64+
int i = 0;
65+
for (Map.Entry<String, String> e : paramAnnotations.entrySet()) {
66+
if (i > 0) json.append(",");
67+
json.append("\"").append(e.getKey()).append("\":\"").append(e.getValue()).append("\"");
68+
i++;
69+
}
70+
json.append("}");
71+
}
72+
73+
json.append("}");
74+
return base + json.toString();
75+
}
76+
77+
return base;
2378
}
2479
}

0 commit comments

Comments
 (0)