@@ -25,10 +25,22 @@ def carve_regex(self):
2525 r"<input.+__VIEWSTATE\"\svalue=\"(.+)\"[\S\s]+<input.+__VIEWSTATEGENERATOR\"\svalue=\"(\w+)\""
2626 )
2727
28- def carve_to_check_secret (self , s , url = None ):
28+ def carve_to_check_secret (self , s , url = None , ** kwargs ):
2929 if len (s .groups ()) == 2 :
30- r = self .check_secret (s .groups ()[0 ], s .groups ()[1 ], url )
31- return r
30+ viewstate = s .groups ()[0 ]
31+ generator = s .groups ()[1 ]
32+
33+ possible_userkey_cookies = ["ASP.NET_SessionId" , "__AntiXsrfToken" , "ASPSESSIONID" ]
34+
35+ if kwargs .get ("cookies" ) and hasattr (kwargs .get ("cookies" ), "get" ):
36+ for cookie_name in possible_userkey_cookies :
37+ if cookie_name in kwargs .get ("cookies" ):
38+ cookie_value = kwargs .get ("cookies" ).get (cookie_name )
39+ r = self .check_secret (viewstate , generator , url , cookie_value )
40+ if r :
41+ return r
42+
43+ return self .check_secret (viewstate , generator , url )
3244
3345 @staticmethod
3446 def valid_preamble (sourcebytes ):
@@ -92,7 +104,8 @@ def viewstate_decrypt(self, ekey_bytes, hash_alg, viewstate_B64, url, mode):
92104 else :
93105 continue
94106
95- def viewstate_validate (self , vkey_bytes , encrypted , viewstate_B64 , generator , url , mode ):
107+ def viewstate_validate (self , vkey_bytes , encrypted , viewstate_B64 , generator , url , mode , viewstate_userkey = None ):
108+
96109 original_vkey_bytes = vkey_bytes
97110 viewstate_bytes = base64 .b64decode (viewstate_B64 )
98111
@@ -104,19 +117,32 @@ def viewstate_validate(self, vkey_bytes, encrypted, viewstate_B64, generator, ur
104117 signature_len = len (vs .signature )
105118 candidate_hash_algs = self .search_dict (self .hash_sizes , signature_len )
106119
120+ modifier_bytes = b"\x00 " * 4
121+ if viewstate_userkey and viewstate_userkey .strip ():
122+ modifier_bytes += viewstate_userkey .encode ("utf-16le" )
107123 for hash_alg in candidate_hash_algs :
108124 vkey_bytes = original_vkey_bytes
109125 viewstate_data = viewstate_bytes [: - self .hash_sizes [hash_alg ]]
110126 signature = viewstate_bytes [- self .hash_sizes [hash_alg ] :]
111127 if hash_alg == "MD5" :
112- md5_bytes = viewstate_data + vkey_bytes
113128 if not encrypted :
114- md5_bytes += b"\x00 " * 4
129+ if viewstate_userkey and viewstate_userkey .strip ():
130+ md5_bytes = b"will not work, sorry"
131+ # MD5 + ViewStateUserKey is a horrible edge case that may NEVER work. We will not match on it, currently.
132+ # Last attempt:
133+ # md5_bytes = viewstate_data + vkey_bytes + page_hash_bytes + viewstate_userkey.encode('utf-16le')
134+ # But page_hash_bytes is apparently NOT the generator and my have to be brute-forced.
135+ # Probably not worth it for the 3 servers in the entire world probably using these settings in the wild.
136+ else :
137+ md5_bytes = viewstate_data + vkey_bytes + modifier_bytes
138+ else :
139+ md5_bytes = viewstate_data + vkey_bytes
115140 h = hashlib .md5 (md5_bytes )
116141 else :
117142 vs_data_bytes = viewstate_data
118143 if not encrypted :
119144 vs_data_bytes += generator
145+ vs_data_bytes += modifier_bytes [4 :]
120146 if mode == "DOTNET45" and url :
121147 s = Simulate_dotnet45_kdf_context_parameters (url )
122148 label , context = sp800_108_get_key_derivation_parameters (
@@ -129,7 +155,9 @@ def viewstate_validate(self, vkey_bytes, encrypted, viewstate_B64, generator, ur
129155 self .hash_algs [hash_alg ],
130156 )
131157
132- if h .digest () == signature :
158+ computed_hash = h .digest ()
159+
160+ if computed_hash == signature :
133161 return hash_alg
134162
135163 return None
@@ -139,6 +167,7 @@ def resolve_args(self, args):
139167 generator_pattern = re .compile (r"^[A-F0-9]{8}$" )
140168
141169 url = None
170+ viewstate_userkey = None
142171 generator = "0000"
143172
144173 for arg in args :
@@ -147,14 +176,15 @@ def resolve_args(self, args):
147176 generator = arg
148177 elif url_pattern .match (arg ):
149178 url = arg
150-
179+ else :
180+ viewstate_userkey = arg
151181 # Remove query string from the URL, if any
152182 if url :
153183 url = urlsplit (url )._replace (query = "" ).geturl ()
154- return generator , url
184+ return generator , url , viewstate_userkey
155185
156186 def check_secret (self , viewstate_B64 , * args ):
157- generator , url = self .resolve_args (args )
187+ generator , url , viewstate_userkey = self .resolve_args (args )
158188
159189 if not self .identify (viewstate_B64 ):
160190 return None
@@ -182,7 +212,7 @@ def check_secret(self, viewstate_B64, *args):
182212
183213 for mode in ["DOTNET40" , "DOTNET45" ]:
184214 validationAlgo = self .viewstate_validate (
185- binascii .unhexlify (vkey ), encrypted , viewstate_B64 , generator , url , mode
215+ binascii .unhexlify (vkey ), encrypted , viewstate_B64 , generator , url , mode , viewstate_userkey
186216 )
187217 if validationAlgo :
188218 if encrypted :
@@ -201,6 +231,8 @@ def check_secret(self, viewstate_B64, *args):
201231 product_string = f"Viewstate: { viewstate_B64 } "
202232 if generator != "0000" :
203233 product_string += f" Generator: { generator [::- 1 ].hex ().upper ()} "
234+ if viewstate_userkey :
235+ product_string += f" ViewStateUserKey: { viewstate_userkey } "
204236 return {"secret" : result , "product" : product_string , "details" : f"Mode [{ mode } ]" }
205237 return None
206238
0 commit comments