[MSSQL] Implements Channel Binding Token - #1986
Conversation
|
@Dfte thanks, this PR looks awesome! |
|
Hey @anadrianmanrique ! Got it! Just to be sure, can I leave the comments ? I believe commenting while make it much more easier to understand for futur maintainers. At least I would have not last 3 days understanding the code if I had that much comments and the TLS handshake in-memory part is quite complicated as well so... Let me know! I'll revert the others things :) |
|
Heyo! So I have removed the unecessary refactoring/code updates only to leave the code I modified myself and the comments I added to make it easier to understand. Let me know if you need anything ;)! |
|
@Dfte thanks! Comments are more than ok! I was referring specifically to code changes |
|
Hey @Dfte hello! (I guess we should define and ty to a codingstandard moving forward to solve this) |
|
All right! Let me know if I can help, next time I'll push things I'll make sure not to change cosmetics ;)! |
…rity in the review
…rity in the review
…rity in the review
|
Checking the new functions... I think we may be missing some data in the send/receive functions (writing while understanding that, so perhaps not fully clear right now) tls_send
If encrypted data length is > 4k we may be missing to send some bytes |
|
About the tls_send function, it's not entirely true. The code actually loops and tries to read 4096 bits of encrypted data that is sent right after via the socket.sendall function. If we have a SSLWantReadError or WriteError then that means the TLS context gave us all the encrypted data which is why I break out of the while True condition. That's pretty much the same with tls_recv, the only difference is that the loop is handled directly witthin the recvTDS function. Inside the TDS structure there is the "Lenght" field that tells us how many bits are sent, so the recvTDS function loops until it get all the data then calls socketRcv that dispatches the data depending of if there is a TLS context or not. So far I didn't have troubles using these functions but I'll try to add data into a dabtase and retrieve the content to make sure everything works as expected :)! So I have tried uploading PsExec using the upload command and it works correctly: Even thought there is an error at the end, the base64 file is uploaded: And it corresponds the legitimate tool once decoded: |
|
Hey, yes, you are right! I still doubt with the Will try to check this function in practice to validate this. Also will start doing some tests with different server versions. Will let you know how those go |
|
Actually I believe it does in the recvTDS: while status != TDS_STATUS_EOM:
if remaining is not None:
tmpPacket = TDSPacket(remaining)
else:
tmpPacket = TDSPacket(self.socketRecv(packetSize))
packetLen = tmpPacket["Length"] - 8
while packetLen > len(tmpPacket["Data"]):
data = self.socketRecv(packetSize)
tmpPacket["Data"] += data
remaining = None
if packetLen < len(tmpPacket["Data"]):
remaining = tmpPacket["Data"][packetLen:]
tmpPacket["Data"] = tmpPacket["Data"][:packetLen]
status = tmpPacket["Status"]
packet["Data"] += tmpPacket["Data"]
packet["Length"] += tmpPacket["Length"] - 8TDS_STATUS_EOM indicates it's the last packet being sent by the MSSQL server ^^ |
|
I think there is something wrong with that. Let me share my tests real quick...
I guess we are not handling ok the Side note, same scenario but with upload command, works fine |
|
@Dfte, just pushed the debug logging I used to check that... will revert once we check it |
|
Alright I'll take a look at that later this afternoon :)! Thanks for the feedback! |
|
Hey @gabrielg5 , I have been trying to repoduce the error and realized it might not be related with my PR. Here I enabled TLS only (no CBT required):
And I get an error as well: So there is definitely an issue here but may be not from my PR but rather the original code. I'll try to debug that right now :)! |
|
yep, performed same test in |
|
But I was actually able to upload xD, what happened ahah but the error comes from mssqlshell. Debugging it right now ! |
|
mm perhaps some of the SQL functions we are calling in those actions? |
|
Got some news! For some reason the error is located in that part of the code (the one from my PR): while status != TDS_STATUS_EOM:
print("LAST PACKET FOUND")
if remaining is not None:
tmpPacket = TDSPacket(remaining)
else:
print(packetLen)
tmpPacket = TDSPacket(self.socketRecv(packetSize))
packetLen = tmpPacket["Length"] - 8
while packetLen > len(tmpPacket["Data"]):
data = self.socketRecv(packetSize)
tmpPacket["Data"] += data
remaining = None
if packetLen < len(tmpPacket["Data"]):
remaining = tmpPacket["Data"][packetLen:]
tmpPacket["Data"] = tmpPacket["Data"][:packetLen]
status = tmpPacket["Status"]
packet["Data"] += tmpPacket["Data"]
packet["Length"] += tmpPacket["Length"] - 8Removing it does allow you downloading the entire file: mssqlclient.py WHITEFLAG.LOCAL/Administrateur:'Defte@WF'@adcs.whiteflag.local -windows-auth -debug -c "download C:\\Windows\\temp\\PsExec.exe.b64 /tmp/PsExec.exe"The reason the download fails is probably because, for some reason, the server sends a TDS status = 0 which triggers the while loop. Thing is, it's not the last packet. Thus the error "SSL.WantReadError". Now I need to find out why tf the status sent by the MSSQL server is 0 xD EDIT: instead of removing the entire while loop, you can edit line 828: status = packet["Status"]
status = 1Okay I got a better idea of what's going on, basically the file is so huge MSSQL splits it into multiples TCP packets. Thus we do miss a loop reading all the pacekts. |
|
Note that upload still work with my PR. However there is still that error: mssqlclient.py WHITEFLAG.LOCAL/Administrateur:'Defte@WF'@adcs.whiteflag.local -windows-auth -debug -c "upload /opt/tools/exe/magnet_ram.exe C:\\Windows\\temp\\magnet_ram.exe"This is probably due to the fact that the account running the MSSQL database is underprivileged (virtual MSSQL account). |
|
Just wondering, could it be related with the fact that package has to be decrypted all at once? I mean, receiving all the encrypted packages and once all have been received, decrypt |
|
I have no clue yet :/... This is beyond my PR but I'll try to take a look ASAP, not before monday tho :/ |
|
Here is a small update, if mssqlshell.py wasn't able to find the file after upload, it's because there are language hardcoded values that weren't found. Looking at this code: print("EXEC xp_fileexist '" + remote_path + ".b64")
result = self.sql_query("EXEC xp_fileexist '" + remote_path + ".b64'")
print(remote_path, result)
if result[0].get('File Exists') != 1:
print("[-] Error uploading file. Check permissions in the configured remote path")
return
print("[+] Uploaded")We can see that it expects a key to be named "File exists" but looking at my MSSQL server configured in french I get the following SQL response: Means modifying "File exists" with "Ce fichier existe déjà", upload works: Yet there is still the reading issue. Working on it :)! |
|
Disabling TLS, I realized that I was able to download files corectly. So yeah this is related to the TLS context somehow... |
|
GOT ITTTTTT!! You were right @gabrielg5 , the TLS _recv function wasn't reading enough data, here is the patch: def tls_recv(self, bufsize):
while True:
try:
# Try to read decrypted data first
decrypted = self.tlsSocket.read(bufsize)
if decrypted:
return decrypted
except ssl.SSLWantReadError:
pass # Means we need more encrypted bytes
# Read more encrypted bytes from the socket
encrypted = self.socket.recv(bufsize)
if not encrypted:
# Remote closed the connection
return b""
self.in_bio.write(encrypted)Now having both TLS and CBT required: We can download files: And also upload: Nice catch! Do you mind reverting the changes and testing the patched function ? I'm sorry I don't have much time right now.... :/ |
|
hey @Dfte, great!! Been doing some tests with different SQLServer versions (2016, 2017, 2019, 2022) and all worked fine Will update the codebase to test this patch for big messages Will let you know how it goes |
|
It's working great! I also found that
is also failing in SQLServer 2016 Will create this issue and the one you mentioned with the "File Exists" column so they can be worked on in separate tickets. Will merge this one. Thank you!! |
|
Glad to hear that it works!! Once this PR is merged, I'll push two others:
And once all of that is done, I'll integrate TDS 8 as well and we should have all MSSQL functions and securities handled!! :) |
|
TLS Channel Binding is linked to: |




















Hello,
The following PR contains all the work I have done on the TDS script which:
The code was entirely commented so that pretty much all the authentication logic is clear and can be understood (and a blogpost is going to be released ASAP for more information)
As of now, this PR allows mssqlclient.py to support pretty much all hardening configuration including encryption and channel binding except TDS 8 which I will push later.
Please note that I only tested the code on a Microsoft SQL Server (160 3232) (windows server 2022) and made sure that:
For anyone who's willing to play with the PR, here is how you can enable encryption on the sql panel:
And how you can enable the Channel Binding Token check:
In the upcomming weeks I'll test this PR during assessments to make sure that it does work with older MSSQL version. Until then, let me know if you need anything :)!