Difference between revisions of "Secure programming"

From Lazarus wiki
Jump to navigationJump to search
Line 216: Line 216:
 
       '''end''';
 
       '''end''';
 
        
 
        
     '''if''' (ch = #8) '''then''' // backspace
+
     '''if''' (ch = #8) '''and''' (Length(Line) <> 0) '''then''' // backspace
 
       '''begin'''
 
       '''begin'''
 
       Line := copy (Line, 1, Length (Line) -1);
 
       Line := copy (Line, 1, Length (Line) -1);

Revision as of 20:01, 26 March 2005

General Info

When developing a program, it is likely that it will interact with the user in some way, even if that means only reading files in the system and presenting the data.

Usually at schools and at universities when one starts to write programs, that person learns how to receive input, while teachers usually say to that person “assume that the data you receive is valid�?. That's when the problems begin.

From the second that a program receives an input, we can not trust any unknown input that we can not control.

Reading from a file is reading an untrusted input, and so is reading users input, or accepting input from a network for example.

Why can't I trust an input ?

In order to understand why an input is dangerous, we first need to understand what is an input.

An input can be from a key stroke, and mouse movement or mouse button clicks, or from reading and accepting information from many other ways like a data stream or even system functions.

It does not matter what is the type of input, because the user can give us wrong input, and the reasons can be intentional or a mistake. You can not control this input, and the main reason is that you can't guess what will the input be.

The result could be an empty (NULL) “data�? that the user provide us, an out of range number or bigger amount of chars we expected, or even an attempt to change the address of the variable that accepts the input from the user. We just can not know what the user is going provide.

Any “unsafe�? handle of the user input can cause for retrieving vital information that the user must not accept, and could not accept, or modification of data that the user could not do any other way, or even break the program itself.

What type of problems can we expect ?

On every type of bug you probably will find a type of attack, but I wish to give a small list of very common type of attacks, instead of writing a lot of the attack types.

The most common attack types are:

Buffer Overflow

When a given data overflows the amount of memory that was allocated for it:

var
iNums : array of integer;
....
SetLength (iNums, 10);
FillChar (iNums[-1], 100, #0); ....
for i := -10 to 10 do
readln (iNums[i]);
....

In this example we can see that for the open array of iNums we gave the ability to accept only 10 numbers, while we entered to the variable a content of 21 numbers.

If the user will try to execute an arbitrary code in one of our attempts he or she will succeed in doing so, because we went outside the buffer that was given to us. And that's a buffer overflow.

DoS Attack

Denial of Service is not only a network problem, but can exists also in many other ways:

procedure Recurse;
begin
while (True) do
begin
Recurse;
end;
end;

This procedure will run until the system will be out of resources to allocate more stack memory to run, and will cause the system to stop responding, or even crash. Altho some systems like Linux, will try to give you the ability to stop running the program, it will take a lot of time from you to do it.

Please note that this is only a static example, but we made a DoS attack on a system that will run the code.


Another known DoS attack is the lack of freeing system resources such as memory, sockets, file descriptors etc...

For example:

 ...
begin
while (True) do
begin
Getmem (OurPtr, 10);
OurPtr := Something;
end;
end.

This example displays a memory allocation (Getmem is like the C malloc), but we exit the execution without freeing the memory at the end of it's use.


Injections

When the user gives us an input, and we are working on the given input directly without sanitizing it, the user can place in some SQL tags or code (like script code, or machine code) for example, that will cause our program to delete some records/tables or send the user some restricted data such as database/table structure, database user and password, content of directory or file, or even execute a program at the computer.

A SQL injection example:

User Input:
  Please enter your name: a' OR 1=1
Inside the code:
  ...
write ('Please enter your name: ');
readln (sName);
Query1.SQL.Add ('SELECT Password FROM tblUsers WHERE Name='#32 + sName + #32);
...

This addition of SQL statement will cause our query to add new “WHERE�? rule that can cause for data traversal or other problems that we are not always able to detect.

Myth and Assumptions

Many of the security issues exists because of ignoring important warnings and information that was given by the compiler, and by thinking that their program does not contain any problem that some one can take advantage.

Here are some examples for this type of problem:

Myths:

  • Security by Obscurity - When no one knows about a problem no one can take advantage of it.
  • Secure programming language - There are languages such as Perl that many people think that they are secure from Buffer overflows and other vulnerabilities while that does not make it so.
  • Hash password is secure - A file that have an hashed password is not secure. Hash can only passed one and you can not retrieve the original data.
  • Nothing can break my program.

Assumptions:

  • The QA team will find and fix my bugs.
  • The user will not harm my program and it's data.
  • My program will be used only for it's original use.
  • All exceptions can remain unhandled.

Explanation

Now after we know some problems we can encounter when developing programs, we should learn how to fix this problems. All of the problems we saw above manifest into two types of problems, assumptions and the lack of care programming. And for learning how to fix them, we first need to learn to think in different approach, that we have.

Overflow

For fixing overflow of data, like buffers and other type of input, we first of all need to identify the type of data we need to work with.

Buffer overflow

If we return to our example of:

var

  iNums : array of integer;

  ....

  SetLength (iNums, 10);

  FillChar (iNums[-1], 100, #0);
  ....

  for i := -10 to 10 do

     readln (iNums[i]);

  ....

We see here a range that was override by our values ,without even checking if the index number is correct.

In dynamic/open arrays in Pascal we can know the limits of the allocated memory. So all we need to do is check if the size is too small or too big for our buffer, and limit the accepting for the size we wish it to be.

So the example should be changed into:

var

  iNums : array of integer;

  ....

  SetLength (iNums, 10);

  FillChar (iNums[Low(iNum)], High(iNum), #0);
  ....

  for i := Low (iNum) to High (iNum) do

     readln (iNums[i]);

  ....

But wait Something is not right yet !

The readln will accept an unlimited amount of chars, and no one is promise us that it will be an Integer or even in the range we can handle.

Number Overflow

While string in Pascal is pure array (hrmm hrmm.. not really, at least not in FPC, but lets pretend it is for a second OK ?) so readln will try to find and see what are it's limits and will not try to overflow the range we gave that type, but Numbers are not the same.

Numbers have limits, a computer have limits of many kinds regarding memory and numbers. It can give only “small�? amount of memory for numbers (floating point and integer numbers). And many times we do not need a large range of numbers to use (like boolean variable that needs only two numbers usually).

In the above example we may have a buffer overflow that will cause a range check error that will give us the wrong number (Carry Flag reminder issues... I'm not going to explain them in here), and we also have a DoS effect, because our program will halt from that point.

So what can we do from that point ?

First of all we may wish to work in that point with a string variable that will be in the length of the largest number +1 (for minus sign), or we can create our own readln procedure/function that will specialize with the Integer type.

For the first offer we can do the following (Copied from the FPC documentation):

Program Example74;

{ Program to demonstrate the Val function. }
Var I, Code : Integer;

begin
  Val (ParamStr (1),I,Code);
  If Code<>0 then 
    Writeln ('Error at position ',code,' : ',Paramstr(1)[Code])
  else
    Writeln ('Value : ',I);  
end.

Here we see how to convert a string into a string with a very easy error handeling. The function StrToInt may also do the trick but it then we need to capture an exception in any error dealing.


Here is a small example for a small readln like procedure for integer numbers.

program MyReadln;
uses CRT;

procedure MyIntReadLn (var Param : Integer; ParamLength : Integer);
var
  Line  : string; 
  ch    : char;
  Error : Integer;
  
begin
  Line  := ;
  
  repeat
    ch := readkey;
    if (Length (Line) <> ParamLength) then
     begin
      if (ch in ['0'..'9']) then
       begin
         Line := Line + ch;
         write (ch);
       end
      else
      if (ch = '-') and (Length (Line) = 0) then
       begin
         Line := '-';
         write (ch);
       end;
      end;
      
    if (ch = #8) and (Length(Line) <> 0) then // backspace
     begin
      Line := copy (Line, 1, Length (Line) -1);
      gotoxy (WhereX -1, WhereY);
      write (' ');
      gotoxy (WhereX -1, WhereY);
     end;
  until (ch = #13);

  val (Line, Param, Error);

  if (Error <> 0) then
    Param := 0;

 writeln;
end;

var
 Num : Integer;

begin
  write ('Number: ');
  MyIntReadLn (Num, 2);
  writeln ('The number is: ', Num);
end.
 

Please note that you can make it even better, and more efficient if you wish. This is only a very small example for how to do it.