Forum Discussion
Russell_Moore_8
Nimbostratus
May 10, 2012ActiveSync Windows Phone query decode
I used the following rule to decode and locate the Windows Phone device ID for access control to an ActiveSync/OWA service. The AS protocol allows the query to be plain text or base64 encoded hex. If you search Google for base64 ActiveSync you'll find the specification for this query method.
This rule works but I am studying it for optimization as I find it a bit ugly myself.
Feedback welcome! (The following code may contain snippets from other contributors for which I do not take credit but thank those contributors)
when HTTP_REQUEST {
create variable to contain the query string
set string_b64encoded [HTTP::query]
test the contents of the query string to see if it is base64 and if so place the content
in a variable
if {[catch {b64decode $string_b64encoded} string_b64decoded] == 0 and $string_b64decoded ne ""}{
scan the decoded content for the Device ID length
the "x4H2" format says to move forward 4 bytes and select the next 2 places
as HEX and put them in varible IDlenHEX
binary scan $string_b64decoded x4H2 IDlenHEX
convert HEX to decimal
scan $IDlenHEX %x IDlenDEC
multiply by two to get the correct character count
set IDlen [expr "$IDlenDEC * 2"]
knowing the DeviceID starts at the 6th pair we move "x5" to the that starting place
then select "H$IDlen" to put in variable HEXdeviceID
binary scan $string_b64decoded x5H$IDlen HEXdeviceID
try to match the found ID to a data group of allowed IDs
if { [matchclass $HEXdeviceID contains allowHEXdeviceIDs] } {
pool ASOWA.example.com_443
log local0. "Found ALLOWED Hexadecimal DeviceID: $HEXdeviceID"
} else {
log local0. "Found DENIED Hexadecimal DeviceID: $HEXdeviceID"
discard
}
}
}
11 Replies
- Russell_Moore_8
Nimbostratus
when HTTP_REQUEST { create variable to contain the query string set string_b64encoded [HTTP::query] test the contents of the query string to see if it is base64 and if so place the content in a variable if {[catch {b64decode $string_b64encoded} string_b64decoded] == 0 and $string_b64decoded ne ""}{ scan the decoded content for the Device ID length the "x4H2" format says to move forward 4 bytes and select the next 2 places as HEX and put them in varible IDlenHEX binary scan $string_b64decoded x4H2 IDlenHEX convert HEX to decimal scan $IDlenHEX %x IDlenDEC multiply by two to get the correct character count set IDlen [expr "$IDlenDEC * 2"] knowing the DeviceID starts at the 6th pair we move "x5" to the that starting place then select "H$IDlen" to put in variable HEXdeviceID binary scan $string_b64decoded x5H$IDlen HEXdeviceID try to match the found ID to a data group of allowed IDs if { [matchclass $HEXdeviceID contains allowHEXdeviceIDs] } { pool ASOWA.example.com_443 log local0. "Found ALLOWED Hexadecimal DeviceID: $HEXdeviceID" } else { log local0. "Found DENIED Hexadecimal DeviceID: $HEXdeviceID" discard } } } - Russell_Moore_8
Nimbostratus
Updated to look for either base64 encoded HEX or plain DeviceID querywhen HTTP_REQUEST { set string_b64encoded [HTTP::query] if {[catch {b64decode $string_b64encoded} string_b64decoded] == 0 and $string_b64decoded ne ""}{ binary scan $string_b64decoded x4H2 IDlenHEX scan $IDlenHEX %x IDlenDEC set IDlen [expr "$IDlenDEC * 2"] binary scan $string_b64decoded x5H$IDlen HEXdeviceID set string_sentid [string toupper $HEXdeviceID] log local0. "EAS deviceID was HEX: $string_sentid" } else { set string_sentid [string toupper [URI::query [HTTP::uri] "DeviceId"]] } set string_URI [string tolower [HTTP::uri]] set string_useragent [string toupper [HTTP::header User-Agent]] set string_requestmethod [HTTP::method] if { $string_sentid != "" && [class match $string_sentid contains EASDeviceIDAllowed] } { log local0. "EAS Device ID match ALLOWED: URI: $string_URI DeviceID: $string_sentid Agent: $string_useragent Method: $string_requestmethod" pool AS-owa.example.com_443 } else { log local0. "EAS default no match DENIED: $string_URI DeviceID: $string_sentid Agent: $string_useragent Method: $string_requestmethod" discard } } - Russell_Moore_8
Nimbostratus
Warning. Currently in 10.2.3 the following code will leak memory.
if {[catch {b64decode $string_b64encoded} string_b64decoded] == 0 and $string_b64decoded ne ""} - hoolio
Cirrostratus
Hi Russell,
Thanks for the note. Do you have more details and/or a BZ ID describing the memory leak?
Thanks, Aaron - hoolio
Cirrostratus
Actually, it looks like this is it:
BZ387625: b64decode leak resources if decode operation fails.
Aaron - Russell_Moore_8
Nimbostratus
Aaron,
That's the ID I was sent too. I was provided a sample code that could be a work around but we are determining if we want to use it or wait for a patch to fix the leak.
This is the sample and reasoning I was given:
A base64 query string length should be always divisible by 4. So,
when HTTP_REQUEST {
catch {
if { ![expr [string length [HTTP::query]]%4] } {
b64decode [HTTP::query]
} else {
puts "Bad 64base input."
}
}
}
I'm not sure about the logic here as it would seem to read "if the query string length is NOT divisible by 4 then base64 decode". This seems to be reverse of what would be needed. - hoolio
Cirrostratus
That's a novel sanity check you could add. But it still doesn't guarantee you'll avoid the memory leak. Basically, it checks if the query string length is evenly divisible by four. If it is, then the assumption is that it's a base64 encoded string. If it's not, then the input isn't attempted to be decoded.
You might also want to add a check to see if there is a query string first as 0%4 will return 0 and trigger decoding in the above example.
if { [HTTP::query] ne "" and ![expr [string length [HTTP::query]]%4] } {
Also, is there a specific parameter name that will have the base64 encoded string? Or is there another check you could add like of the HTTP::path to reduce the chance that you'll attempt to base64 decode a non-base64 encoded string?
Aaron - Russell_Moore_8
Nimbostratus
I agree that your additional sanity check would be needed. Since we have a known leak when there is a failure of base64decode I am leery to try anything until I have a patch that would hopefully fix it. We have had at least one SEV1 production incident due to the leak so care is required. The code only impacts a small number of users at the moment so we have a bit more time luxury.
This code and more that I have not shared is to interrogate MS ActiveSync client requests. The idea is that the client wants a white list of devices allowed in the front door and then further security is done as it passes down the layers.
For the majority of the devices the query string would be in plain text and in a format such as this:
/microsoft-server-activesync?param1=val1¶m2=val2 and so on.
The few devices that are using the alternate protocol method look like this:
/microsoft-server-activesync?abGFkamY7bGFzZGprO2ZsZGthajtsa2RmanNhCg=
So I could either try to find something that will always indicate a text string or the option I had used was to find the exception to text which is base64 encoded. Obviously there are other cases too that would include no query string and plain garbage or attack etc.
In the case of the base64 encoded string the first query parameter is the string and it has no value. The parameter is also changes but I don't know by how much. I need to go read the protocol and see if I can find a pattern in the base64 that can be matched as a signature for such a query.
This is interesting and some of it will be incorporated into my solution but it is also a bit moot if the system doesn't leak memory on failed base64 decodes. - hoolio
Cirrostratus
If you do have the ability to wait for a hotfix that seems like the safest and simplest approach. Else, you could try adding more validation to avoid base64 decoding a non-base64 encoded string. It would be crufty, but maybe you could look for more than one = to determine that a query string isn't base64 encoded?
if { [HTTP::query] ne "" and ![expr [string length [HTTP::query]]%4] and [llength [split [HTTP::query] =]] < 2 } {
Aaron - Russell_Moore_8
Nimbostratus
That's another good suggestion. Thanks!
If pressure builds to return the code before the patch is out I will likely do some of this. Actually I am likely to add a bit more sanity anyway.
I just weight how much code to include vs. simplicity and performance.
In any regard, it is Friday and no one in the right mind makes changes they don't have to on Friday. :)
Thanks for all of your suggestions!
Russell
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)Recent Discussions
Related Content
DevCentral Quicklinks
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com
Discover DevCentral Connects
