From 87fa16d79ed0e776448c313204f76736a2b50a94 Mon Sep 17 00:00:00 2001 From: Vincent Cox Date: Mon, 2 Apr 2018 16:53:09 +0200 Subject: [PATCH] Develop (#61) * Update readme file - Table of contents - Releases documentation * improved regexes (#52) * Update readme file (#51) - Table of contents - Releases documentation * Update db_search_words.txt * Update src_search_words.txt * Update exclusion_list.txt * Update src_search_words.txt * Update db_search_words.txt * Owasp integration * Various fixes and improvements - Update wordlists - Converted searchwords code into object oriented code - Comments are now placed in the report * Change port to less common ports Fix for https://github.com/vincentcox/StaCoAn/issues/54 * Fix browser issue + cleaner logging https://github.com/vincentcox/StaCoAn/issues/54 * Added Contributor + screenshot readme - Added Ayowel to top contributor's - Added section "How does the tool works?" * Download demo report immediately when clicking on the link (#57) * Update readme file (#51) - Table of contents - Releases documentation * Download demo report immediately when clicking on the link * Fix mac (#60) Mac issue: https://github.com/vincentcox/StaCoAn/issues/54 * Server fix --- .gitignore | 1 + README.md | 18 ++-- resources/authors/Ayowel.png | Bin 0 -> 5638 bytes resources/pipeline.png | Bin 0 -> 65956 bytes src/config.ini | 7 +- src/config/db_search_words.txt | 28 ++---- src/config/exclusion_list.txt | 4 +- src/config/owasp_static_android.txt | 61 ++++++++++++ src/config/script.py | 17 ++++ src/config/script2.py | 15 +++ src/config/src_search_words.txt | 31 +++--- src/helpers/file.py | 43 +++++---- src/helpers/match.py | 12 ++- src/helpers/project.py | 17 +++- src/helpers/report_html.py | 54 ++++++++++- src/helpers/searchwords.py | 143 +++++++++++++++++++--------- src/helpers/server.py | 13 ++- src/report/html/owasp_logo.png | Bin 0 -> 20237 bytes src/stacoan.py | 54 +++++++---- 19 files changed, 369 insertions(+), 149 deletions(-) create mode 100644 resources/authors/Ayowel.png create mode 100644 resources/pipeline.png create mode 100644 src/config/owasp_static_android.txt create mode 100644 src/config/script.py create mode 100644 src/config/script2.py create mode 100644 src/report/html/owasp_logo.png diff --git a/.gitignore b/.gitignore index fcf2cd8..a60a1be 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ src/report/html/looty.js *.exe src/test-apk/ src/test-apk_apk/ +.temp_thread_file # OS generated files # diff --git a/README.md b/README.md index a20efd0..c91e2ee 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ For the impatient ones, grab the download on the [releases page](https://github. *: note that currently only apk files are supported, but ipa files will follow very shortly.

-An example report can be found here: [example report](https://github.com/vincentcox/StaCoAn/blob/master/resources/example-report.zip) +An example report can be found [here](https://github.com/vincentcox/StaCoAn/raw/master/resources/example-report.zip). + ## Table of Contents @@ -86,6 +87,10 @@ The reports are made to fit on all screens. ![](resources/responsive.gif) +## How does the tool works? + +![Pipeline tool](resources/pipeline.png) + ## Limitations This tool will have trouble with [obfuscated](https://en.wikibooks.org/wiki/Introduction_to_Software_Engineering/Tools/Obfuscation) code. If you are a developer try to compile without obfuscation turned on before running this tool. If you are on the offensive side, good luck bro. @@ -116,17 +121,17 @@ The report will be put inside a folder with a name corresponding to the apk. ``` cd docker ``` - ``` docker build . -t stacoan ``` _Make sure that your application is at the location `/yourappsfolder`._ ``` -docker run -e JAVA_OPTS="-Xms2048m -Xmx2048m" -p 8000:8000 -p 8080:8080 -i -t stacoan +docker run -e JAVA_OPTS="-Xms2048m -Xmx2048m" -p 8888:8888 -p 7777:7777 -i -t stacoan ``` -Drag and drop your application via: http://127.0.0.1:8000. +Drag and drop your application via: http://127.0.0.1:7777. + ### From source ``` @@ -149,14 +154,14 @@ Install the required python packages: pip3 install -r requirements.txt ``` -Run StaCoAn: +Run StaCoAn via commandline: ``` python3 stacoan.py -p yourApp.apk ``` __Or__ if you rather use the drag and drop interface: ``` -python3 stacoan.py -p yourApp.apk --disable-browser --enable-server +python3 stacoan.py ``` ### Building the executable Make sure that you are in the `src` folder. @@ -263,6 +268,7 @@ If the contribution is high enough, you will be mentioned in the `authors` secti + ## License The following projects were used in this project: diff --git a/resources/authors/Ayowel.png b/resources/authors/Ayowel.png new file mode 100644 index 0000000000000000000000000000000000000000..84034e18463f3936133a908c15ec38ed14004eb6 GIT binary patch literal 5638 zcmeHLZ&*_4wx?-M-l?gkoXq}dcXIU1v5^=|MHHse%nEIq(v%}KnxL7Qih?3g)0tC_ z6w`4O70{E8db*98q2fQFMukj;lu1(rOH?8hMHEC~b8zmx-|lmt`{A7D+z)+t*WPRG z{k(g}2*T%Zgvu^Vs&<<8;(2;W_%&fwe6l0=@ z_14Oj;4>d~pSk?Y(GnWuddbil4kdBSap zd7%j6Uh(QIJJ?w`LAxJ&a#qr zVrrFkPRLd&%+wq;!*-?#Nh4G(C4oL2=qi@ng8XP-ss91d87I3JbmwDHYcs}v82|3! zyCA$n!m+5v&3Sr7qfjV*s!+7wtrMEY3!H6nNslit^n?iQC3py7(#?XG-t>ImybdCO zTbau;Iqf7bm4E(WL60lXis@ORvFYtO@ z>mON+$wExP9YvdYb@53%xSE(H7~cJ`GDciZ1;^lsEdJA5+Mw7Ks5pxZchv#jeEEVIdNkEWO8;UxG>KLAW5S+~ zRB+AE?QzsrT{H9}QB`YD15sT$L>oGMwPPs$mg=y*(|iWOo$m5ey{gN1hPubH)2Eh3ShRm+qT>O zYBk``b@?3-HD&woZ8~Ai)+}3`4!OQp$Rl--+NZ?5^}J^bTCfY4%0Xf}FmlzHWmZXn z@J%%|4sdD=Ft4F#n`$FUa)))JBVx{?Cp$`A0QRn`>GB$9X&&Uz#}NDe^-lg{B49t1 z5D^qGCEc37vfPP1r>)#d^)WlxtV8Q7yrf}>;YfQQ%3RJ~xoWDB(emU-Th2^a*bk*r zo>hA$--aU2DKDqW9JePgJJUX$sc9$s4(jAKZ`jlF!1U+Cmu^9CBPfNyDV-G=hzyLn zyy`!{T#=Us?rL$0?Q45?_bvwi8xGtd0y30uGG-K9uwI;4T`CQ_oI0l}sz0*NXfh4f zvWDHqFVO5QBXZJ81W@$yYYDMq1=gM;3ikQgaXzQDX_54zw+LG%JZKw_PX;qE#C=%m z-b~x34&>$q0H_&Jk5z@3)k{;umib<%QA{(*48i!oFmmCBpAVEeuV4KAKTkv_(dWj6 z<11nR%}18GQbZCZ1QZ~st{sSuv&_Hl93sB|SoNP2rT;hXBLR^JsIMbG|BVhQSGKJC zFEtIy^aKs@-Y#^-dd7|Ye82USlbr}9;x%+qLo9t~(lcd%U0Lrg!r}HD`X8y8V;q#bx%gl7>X=GrSvWoFp8->pAbp_d({wW1 znHAM2{$AfXUKTD#%Qco%R|z&&T=JXwmflL3lA{yJWO%aeO~0F1T)t)xVd$-%nwcS) zX7IsIcf1%cD_k`&(P?(>Ip6{l`C=&k$rNo9K?P2&HptPM8SUlVF*`>MCU-fHpQxuK zd2-BC2R+)s?BegTSDv1<$0XmF0HLa5{qJH+ixE_RepUwXG9rxLe{l3bHz~*6!g-*% z2(-Yy7A*c6;jf?2S@f?_DlX;UXDyk)x98D2j6!87T$t;{p}f5`svJFuzCG)x`E|TJ zoLQ$l;Si#<9cf$P^k~`GrT0~xi_?r|pOst|K#0QIZ?r;7gRf=<%g%#4CMO1pXA~`!!GOb69fBvRfb&M5~HMQ&QC6qI|f--+|lV);q3Xi!X^^rYUB4|o_ z%JsFUG{*sdGE-Jk>!^uc4JDr}F!8Oo3kpm$uNcZ9+Q>acC{uc+ss8rAQxHMdH1uI~Cg(*|aS7>bxk+Rm>uQh`n z262D2t89|hS-%m+!KA}-vX8BON*~+d*PDw6NQtuIPl%gsH;XWG^fAIhUyLe8wwlE&4nrewcY2g`{0 zQ+w+l8$Q-+rV0%q<;863XZpc%H@UKA7v73Wq-m{nEp=bHOpMKmJx{ z_oj%KYLNT})bv;U{I@&Z19{6nXGN z7F2B{R}#z0*;eE;aijNIB0TLYtlZzRl@~AjTQD)Eug;T}Mj_?wg9*?R4Z7-t_KaBS zuGp+1#h7j=D|~TgN(OS>Xn5T(a1q;zi#$X)r{rKdemFZH`7kYkEC2Nup_-Iq&30-t zdgvYn4pQt1(PgAIMyBV$e)=HOuzCOT{f_Rcyu?G5(rf^`(Zh9WA=GEhkuG>O; z1&6~&t%`0!TDlWI*Ay&(3rjl~wFR0H-w3zNx#+fKaO9n!NIRaRomGkFme{XAm2gr?dD;VtnK>+f6Nde%+RedT+FCtbitUVdDM5L4R|{e z`U6jvhc7$Dtk+fV>E}3-hh>r%y#>YPrGQeyBr@1B`St`QG4vK-Z2n;S)HKsK8W=3s zRMWE0Sh`}gE@LMmv9NLp)zIZeFv#BsvcBK|x^9AbtN4_Bu|^+3%_y;v z)l31|pTo|D7AuB$oG?VD4~I7O)=grFj4oY+Q(^e4z`LmWJxHM7A`W-BPVEdl1uSu7C(^+G*<8NOA%m+ft+pLH_s+&bR|B41ZNp5~cmW?72^v dgSPelFXAO1ebD|N_J6ma(BQD3x}z7r{X3rY5tIM` literal 0 HcmV?d00001 diff --git a/resources/pipeline.png b/resources/pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..7704db8821d074c4c4668b2a7d06a2fef7721dc6 GIT binary patch literal 65956 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 Psj@J)Isf|i8xQ^qD9FU& literal 0 HcmV?d00001 diff --git a/src/config.ini b/src/config.ini index 8656f8e..2eee595 100644 --- a/src/config.ini +++ b/src/config.ini @@ -8,6 +8,7 @@ log_file = log.html config_folder = config src_search_words = src_search_words.txt db_search_words = db_search_words.txt +owasp_search_words = owasp_static_android.txt exclusion_filename = exclusion_list.txt limit_top_findings = 3 apptypes = [".apk", ".ipa"] @@ -15,10 +16,10 @@ src_filetypes = [".java", ".html", ".xml", ".js", ".plist"] db_filetypes = [".db"] query_importance = 0 code_offset = 3 -server_enabled = 0 +server_disabled = 0 loglevel = 3 [Server] -report_server_port = 8080 -drag_drop_server_port = 8000 +report_server_port = 8888 +drag_drop_server_port = 7777 diff --git a/src/config/db_search_words.txt b/src/config/db_search_words.txt index 112b425..67efbdb 100644 --- a/src/config/db_search_words.txt +++ b/src/config/db_search_words.txt @@ -1,18 +1,10 @@ -password|||10|||triggers unwanted classes like password reset, hence the low score -privatekey|||80 -private_key|||80 -apikey|||75 -http:|||10 -https:|||7 -database_secret|||80 -database_password|||80 -databasepassword|||80 -databasesecret|||80 -(https|http):\/\/.*api.*|||60||| This regex matches any URL containing 'api' -(https|http):\/\/.*test.*|||60||| This regex matches any URL containing 'test' -(https|http):\/\/.*uat.*|||60||| This regex matches any URL containing 'uat' -^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|||40||| Matching IP adresses -^[a-f0-9]{32}$|||70||| MD5 hash -\b([a-f0-9]{40})\b|||70||| SHA1 hash -^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$|||70||| base64 string -Authorization: Basic|||95||| Basic authentication \ No newline at end of file +passw(d|ord)?|||10|||Sensitive parameters which can contain hardcoded credentials. The score is quite low because this query is often a false-positive. +(private|secret|api|aws)[_-]?key|||50|||Sensitive parameters which can contain hardcoded credentials. The score is quite low because this query is often a false-positive. +https?:|||7|||An URL was found, this might be an API endpoint or just a link to documentation. Hopefully the first one. +(db|database)[_-]?(passw(d|ord)?|secret)|||80|||This match probably contains hardcoded database credentials. +https?:\/\/.*(uat|test|api).*|||60||| This regex matches any URL containing 'api|uat|test' +^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|||40|||An IP address was found, this might be a server (where the API is hosted). Or this might be a link to documentation, however chances are low. +^[a-f0-9]{32}$|||70|||An MD5 hash was found! You are lucky because MD5 is easily crack-able. Just google for "MD5 decrypt online" and you will come a long end. +\b([a-f0-9]{40})\b|||70|||A SHA1 hash was found! You are lucky because SHA1 is easily crack-able. Just google for "SHA1 decrypt online" and you will come a long end. +^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$|||70|||A base64 string was found. Go to https://www.base64decode.org/ and paste your value. This decrypted value is in most cases very interesting. +Authorization: Basic|||95|||Basic authentication was found. Apart from the fact that API communication should be with OAUTH or something similar, it contains hardcoded credentials. diff --git a/src/config/exclusion_list.txt b/src/config/exclusion_list.txt index 5504eca..e2d9b5d 100644 --- a/src/config/exclusion_list.txt +++ b/src/config/exclusion_list.txt @@ -1,3 +1,3 @@ http:|||"res","layout"||| Suggested by Adi -(https|http):\/\/.*api.*|||"res","layout"||| Suggested by Adi -http:\/\/schemas\.android\.com\/apk\/res\/android|||||| \ No newline at end of file +https?:\/\/.*api.*|||"res","layout"||| Suggested by Adi +http:\/\/schemas\.android\.com\/apk\/res\/android|||||| diff --git a/src/config/owasp_static_android.txt b/src/config/owasp_static_android.txt new file mode 100644 index 0000000..2bbfb4e --- /dev/null +++ b/src/config/owasp_static_android.txt @@ -0,0 +1,61 @@ +import dexguard.util|RootDetector.isDeviceRooted||||||Root Activity: dexguard root detection code +com.noshufou.android.su|com.thirdparty.superuser|eu.chainfire.supersu|com.koushikdutta.superuser|eu.chainfire.||||||Root Activity: the app may request root/superuser privileges +test-keys|/system/app/Superuser.apk|isDeviceRooted|/system/bin/failsafe/su|/system/sd/xbin/su|"/system/xbin/which"|"su"|RootTools.isAccessGiven|/system/bin/su|/system/xbin/su||||||Root Activity: the app may have root detection capabilities +dalvik.system.DexClassLoader|java.security.ClassLoader|java.net.URLClassLoader|java.security.SecureClassLoader||||||Dynamic Loading: the app can dynamically load classes +dalvik.system.PathClassLoader|dalvik.system.DexFile|dalvik.system.DexPathList|dalvik.system.DexClassLoader|loadDex|loadClass|DexClassLoader|loadDexFile||||||Dynamic Loading: the app can load and manipulate Dex files +getRuntime().exec|getRuntime||||||Dynamic Loading: the app executes system commands +Log[.][vdiwe]|System.out.print||||||Insecure Data Storage: the app logs information +rawQuery|SQLiteDatabase|execSQL|android.database.sqlite||||||Insecure Data Storage: the app uses SQLite Database +content://||||||Insecure Data Storage: Checking for content providers +MODE_WORLD_READABLE|Context.MODE_WORLD_READABLE||||||Filesystem Access: the Object is World Readable by any App. +MODE_WORLD_WRITABLE|Context.MODE_WORLD_WRITABLE||||||Filesystem Access: the Object is World Writable by any app. +MODE_private_ip|Context.MODE_private_ip||||||Filesystem Access: app can write to app directory. +net.URL|openStream|||55|||Insecure Communication: the app URL can connect to http/https/ftp/jar +net.JarURL|JarURL|jar:|||55|||Insecure Communication: The app URL can connect to JAR url +HttpURL|org.apache.http|HttpRequest||||||Insecure Communication: the app initiate HTTP network_communications +javax.net.ssl.HttpsURL|HttpsURL||||||Insecure Communication: the app URL can initiate a HTTPS network_communication +http.client.HttpClient|net.http.AndroidHttpClient|http.impl.client.AbstractHttpClient||||||Insecure Communication: the app facilitates HTTP Requests, network_communications and Sessions +android.webkit||||||Webview Implementation and Javascript: the app uses Webkit +loadData||||||Webview Implementation and Javascript: the app uses WebView and can load HTML/JavaScript +setJavaScriptEnabled|.addJavascriptInterface||||||Webview Implementation and Javascript: Insecure WebView Implementation. +.setWebContentsDebuggingEnabled(true)||||||Webview Implementation and Javascript: remote WebView debugging is enabled +postUrl||||||Webview Implementation and Javascript: the app can perform WebView POST Request +javax.crypto|kalium.crypto|bouncycastle.crypto||||||Insufficient Cryptography: the app uses crypto +org.thoughtcrime.ssl.pinning|PinningHelper.getPinnedHttpsURLConnection|PinningHelper.getPinnedHttpClient|PinningSSLSocketFactory||||||Insufficient Cryptography: Checking for SSL pinning libraries +java.lang.reflect.Method|java.lang.reflect.Field|Class.forName||||||Code Tampering: the app uses java reflection +import dexguard.util|TamperDetector.checkApk||||||Code Tampering: dexguard tamper detection code +import dexguard.util|CertificateChecker.checkCertificate||||||Code Tampering: dexguard signer certificate tamper detection code +import dexguard.util|TamperDetector.checkApk||||||Reverse Engineering: Checking for dexguard tamper detection code +import dexguard.util|CertificateChecker.checkCertificate||||||Reverse Engineering: Checking for dexguard signer certificate tamper detection code +import dexguard.util|DebugDetector.isDebuggerConnected||||||Reverse Engineering: Checking for Checking for dexguard debugger detection code +import dexguard.util|EmulatorDetector.isRunningInEmulator||||||Reverse Engineering: Checking for Checking for dexguard emulator detection code +import dexguard.util|EDebugDetector.isSignedWithDebugKey||||||Reverse Engineering: Signed with Debugkey +java.lang.System|java.lang.Runtime||||||Lack of Code Protection: the app contains native java code +utils.AESObfuscator|getObfuscator||||||Lack of Code Protection: the app uses obfuscation +password =|secret =|username =|key =|||83|||the app contain hardcoded sensitive informations like usernames, passwords, keys etc: Hard coded sensitive information in Application Code (including Crypto) +java.security.MessageDigest|.MessageDigestSpi|MessageDigest||||||Application makes use of Weak Cryptography: the app uses message digest +java.util.Random||||||Application makes use of Weak Cryptography: the app uses an insecure Random Number Generator +javax.net.ssl|TrustAllSSLSocket-Factory|AllTrustSSLSocketFactory|NonValidatingSSLSocketFactory|ALLOW_ALL_HOSTNAME_VERIFIER|.setDefaultHostnameVerifier|NullHostnameVerifier||||||SSL implementation : insecure SSL implementation +onReceivedSslError|.proceed||||||SSL implementation : insecure webview implementation due to certificate errors +(http|https|ftp|ftps)://[^/\n ]*||||||SSL implementation : checking for weak protocols +content.ContentResolver||||||Insecure application permissions: the app queries Database of SMS, Contacts etc. +getSystemService||||||Insecure application permissions: the app gets system services +OpenFileOutput|getSharedPreferences|SharedPreferences.Editor|getCacheDir|getExternalStorageState|openOrCreateDatabase||||||Insecure application permissions: the app performs local file I/O operations +getSubscriberId|getDeviceId|getDeviceSoftwareVersion||||||Device Details: the app gets Device Info +getSimSerialNumber|getSimOperator|getSimOperatorName||||||Device Details: the app gets SIM data +telephony.TelephonyManager||||||Device Details: the app accesses telphony +sendMultipartTextMessage|sendTextMessage|vnd.android-dir/mms-sms|telephony.SmsManager||||||Device Details: the app can send SMS +app.NotificationManager||||||Device Details: the app sends out Android Notifications +getAllCellInfo||||||Location Services: the app gets Cell information +getCellLocation||||||Location Services: the app gets Cell location +android.location|getLastKnownLocation|requestLocationUpdates|getLatitude|getLongitude||||||Location Services: the app gets GPS location +(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)|||50|||Dump any private IP addresses: Location Services +dexguard.util|sDebugDetector.isDebuggable||||||Debugging: Debug is set to TRUE +IRemoteService|IRemoteService.Stub|IBinder||||||Service Hijacking: the app can communicate with other processes +sendBroadcast|sendOrderedBroadcast|sendStickyBroadcast||||||Broadcast Thief: the app sends broadcasts +startActivity\(|startActivityForResult||||||Malicious Activity/Service Launch: the app starts activties +startService|bindService||||||Malicious Activity/Service Launch: the app starts services +ServerSocket|net.ServerSocket|connect[(][)]|||60|||the app opens TCP Server Sockets: Insecure use of network sockets +DatagramSocket|net.DatagramSocket||||||Insecure use of network sockets: the app can open UDP Datagram Sockets +android.util.Base64|.encodeToString|.encode||||||Application makes use of encoding : the app uses Base64 encoding +android.util.Base64|.decode||||||Application makes use of encoding : the app uses Base64 decoding \ No newline at end of file diff --git a/src/config/script.py b/src/config/script.py new file mode 100644 index 0000000..c98ce48 --- /dev/null +++ b/src/config/script.py @@ -0,0 +1,17 @@ +filename="owasp_static_android.txt" +try: + with open(filename, "r") as file: + lines_in_file = file.read().splitlines() +except IOError: + print("could not open file '%s'." % filename) +line_index = 1 +try: + for line in lines_in_file: + line_index = line_index + 1 + if len(line.split('|||'))==3: + print(str(line.split('|||')[2]) + "||||||" + str(line.split('|||')[1]) + "|||" + str(line.split('|||')[0])) + else: + print(str(line.split('|||')[2]) + "|||" + str(line.split('|||')[3]) + "|||" + str( + line.split('|||')[0]) + "|||" + str(line.split('|||')[1])) +except IOError: + print("Format is not readable or file is missing: %s." % filename) \ No newline at end of file diff --git a/src/config/script2.py b/src/config/script2.py new file mode 100644 index 0000000..7a95ac1 --- /dev/null +++ b/src/config/script2.py @@ -0,0 +1,15 @@ +filename="owasp_static_android.txt" +try: + with open(filename, "r") as file: + lines_in_file = file.read().splitlines() +except IOError: + print("could not open file '%s'." % filename) +line_index = 1 +try: + for line in lines_in_file: + line_index = line_index + 1 + + print(str(line.split('|||')[0]) + "|||" + str(line.split('|||')[1]) + "|||" + str( + line.split('|||')[3]) + ": " + str(line.split('|||')[2])) +except IOError: + print("Format is not readable or file is missing: %s." % filename) \ No newline at end of file diff --git a/src/config/src_search_words.txt b/src/config/src_search_words.txt index 8e03daa..5b404ee 100644 --- a/src/config/src_search_words.txt +++ b/src/config/src_search_words.txt @@ -1,20 +1,11 @@ -password|||10|||triggers unwanted classes like password reset, hence the low score -privatekey|||80 -private_key|||80 -apikey|||75 -http:|||10 -https:|||7 -database_secret|||80 -database_password|||80 -databasepassword|||80 -databasesecret|||80 -(https|http):\/\/.*api.*|||60||| This regex matches any URL containing 'api' -(https|http):\/\/.*test.*|||60||| This regex matches any URL containing 'test' -(https|http):\/\/.*uat.*|||60||| This regex matches any URL containing 'uat' -^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|||40||| Matching IP adresses -^[a-f0-9]{32}$|||70||| MD5 hash -\b([a-f0-9]{40})\b|||70||| SHA1 hash -^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$|||70||| base64 string -Authorization: Basic|||95||| Basic authentication -SELECT \* FROM|||40||| Intersting SQL transaction -INSERT INTO .* VALUES|||40||| Intersting SQL transaction \ No newline at end of file +passw(d|ord)?|||10|||Sensitive parameters which can contain hardcoded credentials. The score is quite low because this query is often a false-positive. +(private|secret|api|aws)[_-]?key|||50|||Sensitive parameters which can contain hardcoded credentials. The score is quite low because this query is often a false-positive. +https?:|||7|||An URL was found, this might be an API endpoint or just a link to documentation. Hopefully the first one. +(db|database)[_-]?(passw(d|ord)?|secret)|||80|||This match probably contains hardcoded database credentials. +https?:\/\/.*(uat|test|api).*|||60||| This regex matches any URL containing 'api|uat|test' +^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|||40|||An IP address was found, this might be a server (where the API is hosted). Or this might be a link to documentation, however chances are low. +^[a-f0-9]{32}$|||70|||An MD5 hash was found! You are lucky because MD5 is easily crack-able. Just google for "MD5 decrypt online" and you will come a long end. +\b([a-f0-9]{40})\b|||70|||A SHA1 hash was found! You are lucky because SHA1 is easily crack-able. Just google for "SHA1 decrypt online" and you will come a long end. +^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$|||70|||A base64 string was found. Go to https://www.base64decode.org/ and paste your value. This decrypted value is in most cases very interesting. +Authorization: Basic|||95|||Basic authentication was found. Apart from the fact that API communication should be with OAUTH or something similar, it contains hardcoded credentials. +(SELECT\s[\w\*\)\(\,\s]+\sFROM\s[\w]+)| (UPDATE\s[\w]+\sSET\s[\w\,\'\=]+)| (INSERT\sINTO\s[\d\w]+[\s\w\d\)\(\,]*\sVALUES\s\([\d\w\'\,\)]+)| (DELETE\sFROM\s[\d\w\'\=]+)|||40|||Interesting SQL transaction! In most cases a useless finding, but it might lead to something bigger. Might. diff --git a/src/helpers/file.py b/src/helpers/file.py index e1d12b3..e88ca3a 100644 --- a/src/helpers/file.py +++ b/src/helpers/file.py @@ -6,7 +6,7 @@ from helpers.logger import Logger from helpers.match import MatchDatabase, MatchSource -from helpers.searchwords import Searchwords +from helpers.searchwords import SearchLists class File: @@ -40,18 +40,19 @@ def find_matches_in_db_file(self): line = 0 for row in cursor.fetchall(): line += 1 - for matchword in Searchwords.db_search_words: + for listItem in SearchLists.all_lists["DB_WORDS"].ListCollection: exclude = False - if re.match(File.non_regex_indicator, str(row)): - Searchwords.db_search_words[matchword].regex = True - if re.search(matchword, str(row), re.IGNORECASE): - for item in Searchwords.exclusion_list: - if item[0] == matchword and item[1] in self.file_path: - Logger("Database xclusion found: %s in file %s" % (str(item[0]), self.file_path), Logger.INFO) + # if re.match(File.non_regex_indicator, str(row)): + # Searchwords.db_search_words[matchword].regex = True + if re.search(listItem.searchword, str(row), re.IGNORECASE): + for ExclItem in SearchLists.all_lists["EXCL_WORDS"].ListCollection: + if ExclItem.searchword == listItem.searchword and ExclItem.dir in self.file_path: + Logger("Database exclusion found: %s in file %s" % (str(ExclItem.searchword), self.file_path), + Logger.INFO) exclude = True if exclude == False: - importance = Searchwords.db_search_words[matchword] - db_match = MatchDatabase(matchword, line, str(table_name), str(row), importance) + importance = listItem.importance + db_match = MatchDatabase(listItem.searchword, line, str(table_name), str(row), importance, listItem.comment) self.db_matches.append(db_match) self.all_matches.append(db_match) self.orden_matches() @@ -65,23 +66,23 @@ def find_matches_in_src_file(self, CODE_OFFSET, QUERY_IMPORTANCE): return list() line_index = 1 for line in lines_in_file: - for query in Searchwords.src_search_words.keys(): - if int(Searchwords.src_search_words[query]) > QUERY_IMPORTANCE: - if re.match(File.non_regex_indicator, query): - Searchwords.src_search_words[query].regex = True - if re.search(query, line.lower(), re.IGNORECASE): + for listItem in SearchLists.all_lists["SRC_WORDS"].ListCollection: + if int(listItem.importance) > QUERY_IMPORTANCE: + # if re.match(File.non_regex_indicator, listItem.searchword): + # Searchwords.src_search_words[query].regex = True + if re.search(listItem.searchword, line, re.IGNORECASE): exclude = False - for item in Searchwords.exclusion_list: - if re.search(item[0], line, re.IGNORECASE): - if (item[1] in self.file_path or (item[1] == "" or item[1] is None)): - Logger("Exclusion found: %s in file %s" % (str(item[0]), self.file_path), + for ExclItem in SearchLists.all_lists["EXCL_WORDS"].ListCollection: + if re.search(ExclItem.searchword, line, re.IGNORECASE): + if (ExclItem.dir in self.file_path or (ExclItem.dir == "" or ExclItem.dir is None)): + Logger("SRC exclusion found: %s in file %s" % (str(ExclItem.searchword), self.file_path), Logger.INFO) exclude = True if exclude == False: upper_range = min(line_index + CODE_OFFSET, len(lines_in_file)+1) lower_range = max(line_index - CODE_OFFSET-1, 1) - src_match = MatchSource(query, line_index, lines_in_file[lower_range:upper_range], - Searchwords.src_search_words[query], len(lines_in_file)) + src_match = MatchSource(listItem.searchword, line_index, lines_in_file[lower_range:upper_range], + listItem.importance, len(lines_in_file), listItem.owasp, listItem.comment) self.all_matches.append(src_match) self.src_matches.append(src_match) line_index = line_index + 1 diff --git a/src/helpers/match.py b/src/helpers/match.py index 85b224c..95274f5 100644 --- a/src/helpers/match.py +++ b/src/helpers/match.py @@ -1,23 +1,25 @@ class Match: all_global_matches = list() - def __init__(self, matchword, importance): + def __init__(self, matchword, importance, comment): self.matchword = matchword self.importance = importance + self.comment = comment self.regex = False class MatchSource(Match): - def __init__(self, matchword, line, lines_in_file, importance, total_count_lines): - Match.__init__(self, matchword, importance) + def __init__(self, matchword, line, lines_in_file, importance, total_count_lines, owasp_item, comment): + Match.__init__(self, matchword, importance, comment) self.line = line + self.owasp_item = owasp_item self.lines_in_file = lines_in_file self.total_count_lines = total_count_lines Src_Match.all_global_matches.append(self) class MatchDatabase(Match): - def __init__(self, matchword, line, table, value, importance): - Match.__init__(self, matchword, importance) + def __init__(self, matchword, line, table, value, importance, comment): + Match.__init__(self, matchword, importance, comment) self.table = table self.line = line self.value = value diff --git a/src/helpers/project.py b/src/helpers/project.py index cf2cd94..de241ef 100644 --- a/src/helpers/project.py +++ b/src/helpers/project.py @@ -10,7 +10,7 @@ from helpers.file import File from helpers.logger import Logger -from helpers.searchwords import Searchwords +from helpers.searchwords import SearchLists PATH = os.getcwd() @@ -72,7 +72,18 @@ def frequency_word_list(self): frequency_words[match.matchword] = str(int(frequency_words[match.matchword]) + 1) # Sort OrderedDict according to importance of the searchwords - frequency_words = OrderedDict(sorted(frequency_words.items(), key=lambda t: int(Searchwords.all_searchwords[t[0]]), reverse=True)) + tosort = list() + for freqitem in frequency_words.items(): + for worlists in ["DB_WORDS", "SRC_WORDS"]: + for worditem in SearchLists.all_lists[worlists].ListCollection: + if freqitem[0] == worditem.searchword: + tosort.append([worditem.importance, freqitem]) + # sorteer ok eerste element (importance) en bij gelijkheid sorteren op 2de element (freqitem) + sorted_tosort = sorted(tosort, key=lambda x: (x[0], x[1]), reverse=True) + frequency_words = OrderedDict() + for item in sorted_tosort: + frequency_words[item[1][0]] = item[1][1] + # Limit to top 10 limited_frequency_words = OrderedDict() i = 0 @@ -122,6 +133,8 @@ def app_prepper(self): if not ((self.application_file.startswith("'") and self.application_file.endswith("'") ) or (self.application_file.startswith("\"") and self.application_file.endswith("\"") )): self.application_file = "\"" + self.application_file + "\"" cmd = "\""+os.path.join(os.getcwd(), "jadx", "bin", "jadx") + '\" -d \"' +jadx_folder + "\" " + self.application_file + if os.name == 'Darwin': + cmd = "bash "+cmd Logger(cmd) jadx_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) output_jadx = "--------- JADX OUTPUT BELOW --------- \n " diff --git a/src/helpers/report_html.py b/src/helpers/report_html.py index cc43d67..640b05c 100644 --- a/src/helpers/report_html.py +++ b/src/helpers/report_html.py @@ -12,7 +12,7 @@ from helpers.html_page import Htmlpage from helpers.logger import Logger from helpers.project import Project -from helpers.searchwords import Searchwords +from helpers.searchwords import SearchLists class Report_html(Htmlpage): @@ -104,7 +104,10 @@ def get_source_code_from_file(self, file_path, project): # rating color with self.tag("i", klass="material-icons medium grade-" + str( - Searchwords.all_searchwords[match.matchword])): + next((ListItem.importance for ListItem in + SearchLists.all_lists["DB_WORDS"].ListCollection if + ListItem.searchword == match.matchword), None))): + self.text("report") with self.tag('h5'): if not match.regex: @@ -112,6 +115,17 @@ def get_source_code_from_file(self, file_path, project): # print(match.matchword) else: self.text("regex: " + match.matchword) + # If comment of match is not empty, place the issue description + if not match.comment == "": + with self.tag("ul", klass="collapsible"): + with self.tag("li"): + with self.tag("div", klass="collapsible-header"): + with self.tag("i", klass="material-icons"): + self.text("info") + self.text("Issue Description") + with self.tag("div", klass="collapsible-body"): + with self.tag("span"): + self.text(match.comment) with self.tag("div", klass="card-content"): self.text("table: \""+str(match.table)+"\" : "+ str(match.value)) with self.tag('div', klass="row"): @@ -203,13 +217,42 @@ def get_source_code_from_file(self, file_path, project): # rating color with self.tag("i", klass="material-icons medium grade-" + str( - Searchwords.all_searchwords[match.matchword])): + next((ListItem.importance for ListItem in + SearchLists.all_lists["SRC_WORDS"].ListCollection if + ListItem.searchword == match.matchword), None))): self.text("report") with self.tag('h5'): if not match.regex: + self.text(match.matchword) else: self.text("regex: " + match.matchword) + # If comment of match is not empty, place the issue description + if not match.comment == "": + with self.tag("ul", klass="collapsible"): + with self.tag("li"): + with self.tag("div", klass="collapsible-header"): + with self.tag("i", klass="material-icons"): + self.text("info") + self.text("Issue Description") + with self.tag("div", klass="collapsible-body"): + with self.tag("span"): + self.text(match.comment) + # Place owasp icon + if match.owasp_item: + with self.tag("ul", klass="collapsible"): + with self.tag("li"): + with self.tag("div", klass="collapsible-header"): + with self.tag("img", ('data-position', 'top'), + ('data-tooltip', 'OWASP item'), + src="html/owasp_logo.png", height="20em", style="margin-right: 1rem;margin-left: 5px;", klass="tooltipped"): + pass + self.text(" OWASP Item") + with self.tag("div", klass="collapsible-body"): + with self.tag("span"): + self.text("For more information, have a look at: https://www.owasp.org/index.php/OWASP_Mobile_Security_Testing_Guide") + + with self.tag("div", klass="card-tabs"): with self.tag("ul", klass="tabs tabs-fixed-width", style="background-color: #30363A !important;"): for group_matchword, matchobjects_list in file.grouped_matches.items(): @@ -422,7 +465,10 @@ def html_wordlist(self, project): for word, freq in frequency_words.items(): with self.tag("div", klass="chip"): with self.tag("i", - klass="material-icons grade-"+str(Searchwords.all_searchwords[word])): + klass="material-icons grade-"+str( + next((ListItem.importance for ListItem in + SearchLists.all_lists["ALL_WORDS"].ListCollection if + ListItem.searchword == word), None))): self.text("beenhere") self.text(word + " : " + freq) # get src_files with frequency diff --git a/src/helpers/searchwords.py b/src/helpers/searchwords.py index 353c56e..494487a 100644 --- a/src/helpers/searchwords.py +++ b/src/helpers/searchwords.py @@ -1,26 +1,79 @@ import os import sys from collections import OrderedDict +import copy from operator import itemgetter import configparser from helpers.logger import Logger +class ExclItem: + def __init__(self, searchword, comment, dir): + self.searchword = searchword + self.comment = comment + self.dir = dir -class Searchwords: - all_searchwords = OrderedDict() - # 'src_search_words': Contains all searchwords for source, config files like .java, .xml, .html, .js,... - src_search_words = OrderedDict() - # 'db_search_words': Contains all searchwords for database files - db_search_words = OrderedDict() - # 'exclusion_list.txt': Contains all searchwords which should be excluded in a certain folder - exclusion_list = [] +class SearchItem: + def __init__(self, searchword, importance, comment, owasp): + self.searchword = searchword + self.importance = importance + self.comment = comment + self.owasp = owasp - # Each Searchwords-list needs to be imported. - # 'filename' will be the location of the list which needs to be imported. - # 'search_words' will be linked to: 'src_search_words' or 'db_search_words' - def searchwords_for_type(self, filename, search_words): + +class SearchLists: + all_lists = dict() + def __init__(self): + config = configparser.ConfigParser() + config.read("config.ini") + + src_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), + config.get("ProgramConfig", 'src_search_words')) + owasp_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), + config.get("ProgramConfig", 'owasp_search_words')) + db_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), + config.get("ProgramConfig", 'db_search_words')) + exclusion_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), + config.get("ProgramConfig", 'exclusion_filename')) + + # Import all searchlists into objects in a searchlist + self.all_lists["SRC_WORDS"] = SearchList(src_filename, "SRC_WORDS") + self.all_lists["OWASP_WORDS"] = SearchList(owasp_filename, "OWASP_WORDS") + self.all_lists["DB_WORDS"] = SearchList(db_filename, "DB_WORDS") + self.all_lists["EXCL_WORDS"] = SearchList(exclusion_filename, "EXCL_WORDS") + + # Merge owasp and src + for item in SearchLists.all_lists["OWASP_WORDS"].ListCollection: + SearchLists.all_lists["SRC_WORDS"].ListCollection.append(item) + + # All findings + self.all_lists["ALL_WORDS"] = copy.deepcopy(SearchLists.all_lists["SRC_WORDS"]) + for item in SearchLists.all_lists["DB_WORDS"].ListCollection: + SearchLists.all_lists["ALL_WORDS"].ListCollection.append(item) + + # Sort again on importance + self.all_lists["ALL_WORDS"].sortList() + self.all_lists["SRC_WORDS"].sortList() + + +class SearchList: + def __init__(self, filename, name): + self.ListCollection = [] + if name == "EXCL_WORDS": + self.importExclList(filename) + else: + self.importList(filename) + self.sortList() + + + def addSearchItem(self, searchword, importance, comment, owasp): + item = SearchItem(searchword, importance, comment, owasp) + self.ListCollection.append(item) + def addExclItem(self, searchword, comment, dir): + item = ExclItem(searchword, comment, dir) + self.ListCollection.append(item) + def importExclList(self, filename): try: with open(filename, "r") as file: lines_in_file = file.read().splitlines() @@ -30,16 +83,23 @@ def searchwords_for_type(self, filename, search_words): line_index = 1 try: for line in lines_in_file: - search_words[line.split('|||')[0]] = int(line.split('|||')[1]) + + searchword = line.split('|||')[0] + if len(line.split('|||')) > 2: + comment = line.split('|||')[2] + else: + comment = "" + dir_list_with_quotes = str(line.split('|||')[1]).split(',') + dir_list_without_quotes = [] + for item in dir_list_with_quotes: + dir_list_without_quotes.append(item.strip("\"")) + self.addExclItem(searchword, comment, os.path.join(*dir_list_without_quotes)) line_index = line_index + 1 except IOError: Logger("Format is not readable or file is missing: %s." % filename, Logger.ERROR) sys.exit() - # Sort search words. - search_words = OrderedDict(sorted(search_words.items(), key=itemgetter(1))) - self.all_searchwords.update(search_words) - def exclusion_list_reader(self, filename): + def importList(self, filename): try: with open(filename, "r") as file: lines_in_file = file.read().splitlines() @@ -49,38 +109,29 @@ def exclusion_list_reader(self, filename): line_index = 1 try: for line in lines_in_file: - dir_list_with_quotes = str(line.split('|||')[1]).split(',') - dir_list_without_quotes = [] - for item in dir_list_with_quotes: - dir_list_without_quotes.append(item.strip("\"")) - self.exclusion_list.append([line.split('|||')[0], os.path.join(*dir_list_without_quotes)]) + if line.split('|||')[1]: + searchword = line.split('|||')[0] + if line.split('|||')[1]: + importance = int(line.split('|||')[1]) + else: + importance = 20 + if len(line.split('|||')) > 2: + comment = line.split('|||')[2] + else: + comment = "" + if "owasp_static_android.txt" in filename: + owasp = True + else: + owasp = False + self.addSearchItem(searchword, importance, comment, owasp) line_index = line_index + 1 except IOError: Logger("Format is not readable or file is missing: %s." % filename, Logger.ERROR) sys.exit() - #self.exclusion_list.append(search_words) + pass + + def sortList(self): + self.ListCollection.sort(key=lambda x: x.importance, reverse=True) + - def searchwords_import(self): - config = configparser.ConfigParser() - config.read("config.ini") - # How to extend with other filetypes: - # TYPE_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), - # config.get("ProgramConfig", 'TYPE_search_words')) - src_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), - config.get("ProgramConfig", 'src_search_words')) - db_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), - config.get("ProgramConfig", 'db_search_words')) - exclusion_filename = os.path.join(config.get("ProgramConfig", 'config_folder'), - config.get("ProgramConfig", 'exclusion_filename')) - search_list = dict() - exclusion_list = dict() - search_list[src_filename] = Searchwords.src_search_words - search_list[db_filename] = Searchwords.db_search_words - exclusion_list[exclusion_filename] = Searchwords.exclusion_list - # How to extend (Cont.d) - # search_list[TYPE_filename] = Searchwords.TYPE_search_words - for filename, search_words in search_list.items(): - self.searchwords_for_type(filename, search_words) - for filename, search_words in exclusion_list.items(): - self.exclusion_list_reader(filename) diff --git a/src/helpers/server.py b/src/helpers/server.py index 74302d4..e57690f 100644 --- a/src/helpers/server.py +++ b/src/helpers/server.py @@ -36,11 +36,13 @@ def create_drag_drop_server(): class reportserver(http.server.SimpleHTTPRequestHandler): def log_request(self, code='-', size='-'): - if ".html" in str(self.requestline): + if not any(s in str(self.requestline) for s in + ('lootbox.html', '.ico', 'robots.txt', '.js', '.css', 'start.html', '.woff2', '.png', '.jpg')): Logger(self.requestline + " " + str(code) + " " + str(size), Logger.INFO) def log_error(self, format, *args): - if not "robots.txt" in self.requestline: + if not any(s in str(self.requestline) for s in + ('lootbox.html', 'robots.txt')): Logger(("%s - - [%s] %s\n" % (self.address_string(), self.log_date_time_string(), @@ -57,10 +59,13 @@ class dragdropserver(http.server.BaseHTTPRequestHandler): q = Queue() def log_request(self, code='-', size='-'): - Logger(self.requestline+ " " + str(code) + " " + str(size), Logger.INFO) + if not any(s in str(self.requestline) for s in + ('lootbox.html', '.ico', 'robots.txt', '.js', '.css', 'start.html', '.woff2', '.png', '.jpg')): + Logger(self.requestline+ " " + str(code) + " " + str(size), Logger.INFO) def log_error(self, format, *args): - if not "robots.txt" in self.requestline: + if not any(s in str(self.requestline) for s in + ('lootbox.html', 'robots.txt')): Logger(("%s - - [%s] %s - %s\n" % (self.address_string(), self.log_date_time_string(), diff --git a/src/report/html/owasp_logo.png b/src/report/html/owasp_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6417926384c4e50cd19a0720349ced007ed52b19 GIT binary patch literal 20237 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 M*}2%(;)p5#1CqPNcK`qY literal 0 HcmV?d00001 diff --git a/src/stacoan.py b/src/stacoan.py index 3fc900d..7421b05 100644 --- a/src/stacoan.py +++ b/src/stacoan.py @@ -17,7 +17,7 @@ from helpers.logger import Logger from helpers.project import Project from helpers.report_html import Report_html -from helpers.searchwords import Searchwords +from helpers.searchwords import SearchLists from helpers.server import ServerWrapper @@ -34,7 +34,7 @@ def parse_args(): help='Relative path to the project') parser.add_argument('--disable-browser', action='store_true', required=False, help='Do not automatically open the HTML report in a browser') - parser.add_argument('--enable-server', action='store_true', required=False, + parser.add_argument('--disable-server', action='store_true', required=False, help='Do not run the server to drag and drop files to be analysed') log_group = parser.add_mutually_exclusive_group(required=False) @@ -42,12 +42,21 @@ def parse_args(): log_group.add_argument('--log-errors', action='store_true', help='Log only errors') log_group.add_argument('--log-warnings', action='store_true', help='Log only errors and warning messages') + + # Check if the right parameters are set + args = parser.parse_args() + if args.disable_server and args.project is None: + parser.error("--disable-server requires the input file (application file) specified with -p") + if args.project: + args.disable_server == True + + # return aur args, usage: args.argname - return parser.parse_args() + return args # Note that this server(args) function CANNOT be placed in the server.py file. It calls "program()", which cannot be # called from the server.py file -def server(args, server_enabled, DRAG_DROP_SERVER_PORT): +def server(args, server_disabled, DRAG_DROP_SERVER_PORT): # Windows multithreading is different on Linux and windows (fork <-> new instance without parent context and args) child=False if os.name == 'nt': @@ -57,13 +66,17 @@ def server(args, server_enabled, DRAG_DROP_SERVER_PORT): # you may also want to remove whitespace characters like `\n` at the end of each line content = [x.strip() for x in content] args.project = [content[0]] - args.enable_server = content[1] + args.disable_server = content[1] args.log_warnings = content[2] args.disable_browser = content[3] child = True os.remove(".temp_thread_file") + else: + if os.path.exists(".temp_thread_file"): + child = True + os.remove(".temp_thread_file") - if (server_enabled or args.enable_server or ((not len(sys.argv) > 1))) and (not child): + if (not(server_disabled or args.disable_server) or ((not len(sys.argv) > 1))) and (not child): # This is a "bridge" between the stacoan program and the server. It communicates via this pipe (queue) def serverlistener(in_q): while True: @@ -77,14 +90,18 @@ def serverlistener(in_q): exit(0) # Process the data - args = argparse.Namespace(project=[data], enable_server=False, log_warnings=False, log_errors=False, disable_browser=True) + args = argparse.Namespace(project=[data], disable_server=False, log_warnings=False, log_errors=False, disable_browser=True) # On windows: write arguments to file, spawn process, read arguments from file, delete. if os.name == 'nt': with open('.temp_thread_file', 'a') as the_file: the_file.write(data+"\n") - the_file.write("False\n") # enable_server + the_file.write("False\n") # disable_server the_file.write("False\n") # log_warnings the_file.write("True\n") + else: + with open('.temp_thread_file', 'a') as the_file: + the_file.write("filling") + p = Process(target=program, args=(args,)) p.start() @@ -103,13 +120,9 @@ def serverlistener(in_q): drag_drop_server_thread.daemon = True drag_drop_server_thread.start() - if not args.disable_browser: + if (not args.disable_browser) and not (args.disable_server or server_disabled): # Open the webbrowser to the generated start page. report_folder_start = "http:///127.0.0.1:" + str(DRAG_DROP_SERVER_PORT) - if sys.platform == "darwin": # check if on OSX - # strip off http:/// - report_folder_start = str(report_folder_start).strip("http:///") - report_folder_start = "file:///" + report_folder_start webbrowser.open(report_folder_start) # Keep waiting until q is gone. @@ -132,7 +145,7 @@ def program(args): config = configparser.ConfigParser() config.read("config.ini") - server_enabled = config.getboolean("ProgramConfig", 'server_enabled') + server_disabled = config.getboolean("ProgramConfig", 'server_disabled') DRAG_DROP_SERVER_PORT = json.loads(config.get("Server", 'drag_drop_server_port')) # Update log level @@ -145,14 +158,17 @@ def program(args): config.write(configfile) # Import the searchwords lists - Searchwords.searchwords_import(Searchwords()) + # Searchwords.searchwords_import(Searchwords()) + SearchLists() + # Server(args) checks if the server should be run and handles the spawning of the server and control of it - server(args, server_enabled, DRAG_DROP_SERVER_PORT) + if not args.project: + server(args, server_disabled, DRAG_DROP_SERVER_PORT) # For each project (read .ipa or .apk file), run the scripts. all_project_paths = args.project - + if not all_project_paths: sys.exit(0) for project_path in all_project_paths: @@ -244,7 +260,7 @@ def program(args): print("\n--------------------\n") Logger("Static code analyzer completed succesfully in %fs." % (time() - start_time)) Logger("HTML report is available at: %s" % report_folder_start) - if (not args.disable_browser) and not (args.enable_server or server_enabled): + if (not args.disable_browser) or (args.disable_server or server_disabled): Logger("Now automatically opening the HTML report.") # Open the webbrowser to the generated start page. if sys.platform == "darwin": # check if on OSX @@ -256,6 +272,8 @@ def program(args): sys.exit() if __name__ == "__main__": + if os.path.exists(".temp_thread_file"): + os.remove(".temp_thread_file") multiprocessing.freeze_support() if os.environ.get('DEBUG') is not None: program(parse_args())