Sign in Join
Wiki copytemporary

Secure programming: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Foreword
22
33This wiki page is an attempt to teach a different approach in how to create software. The page uses very simple examples to show that many problems can be taken advantage of in order to create a security attack on a computer, a program or on an entire system.
44
55Please note that the document is only a start to teach how to write better and somewhat more secure code, but it does not attempt to be a complete guide on how to do so. In fact, it's only a brief overview of how we need to see our code and program, and how to avoid many common problems our there.
66
77Please remember that this document is about educating for better coding, not a guide for hacking and cracking programs.
88
99You may also be interested in [[The Power of 10]] for concise advise.
1010
1111## Don't trust input
1212
1313When 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.
1414
1515Usually 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:
1616
1717> **Note:** We can not trust any input that we can not control as its contents are unknown and might exploit a vulnerability in our software.
1818
1919Reading from a file is reading an untrusted input, and so is reading users input, or accepting input from a network for example.
2020
2121### Why can't I trust an input?
2222
2323In order to understand why an input is dangerous, we first need to understand what is an input.
2424
2525An 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. In fact, anything your program gets from the outside is input.
2626
2727It does not matter what the type of input is: for example a user or another system 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 the input will be.
2828
2929The results could be empty (NULL) “data” that the user provides us, a number that is out of our expected range, or a larger amount of chars than 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 to provide.
3030
3131Any “unsafe” handling of input can cause retrieval of critical information that the user is unauthorized to see, or modification of data that the user is not permitted to do, corruption of data, or even breaking (crashing) the program itself.
3232
3333### What type of problems can we expect?
3434
3535On every type of bug you probably will find a type of attack, but I wish to give a small list of very common types of attacks, instead of writing a lot of the attack types.
3636
3737The most common attack types are:
3838
3939#### Buffer Overflow
4040
4141When a given data overflows the amount of memory that was allocated for it:
4242
4343``` pascal
4444var
4545 iNums : array [0..9] of integer;
4646 ...
4747 FillChar (iNums[-1], 100, #0);
4848 ...
4949 for i := -10 to 10 do
5050 readln (iNums[i]);
5151 ...
5252```
5353
5454In this example we can see that for the static array of iNums we gave the ability to accept only 10 numbers, while we entered to the variable a content of 21 numbers.
5555
5656Please note that while the compiler might warn in simple cases, it won't in more elaborate forms.
5757
5858If the user can input data that is sent to the buffer, he can supply some values that can be interpreted as machine code instructions, which will be written outside our buffer. The computer could then execute this code instead of the code that should have been there. That's a buffer overflow.
5959
6060#### DoS Attack
6161
6262Denial of Service is not only a network problem, but can exist also in many other ways:
6363
6464``` pascal
6565procedure Recurse;
6666begin
6767 while (True) do
6868 begin
6969 Recurse;
7070 end;
7171end;
7272```
7373
7474This procedure will run until the system is out of resources as it allocates more stack memory every recursion, and will cause the system to stop responding, or even crash. Although 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.
7575
7676Please note that this is only a static example, but we made a DoS attack on a system running this code.
7777
7878Another known DoS attack is the lack of freeing system resources such as memory, sockets, file descriptors, etc...
7979
8080For example:
8181
8282``` pascal
8383...
8484 begin
8585 while True do
8686 begin
8787 Getmem(OurPtr, 10);
8888 OurPtr := Something;
8989 end;
9090 end.
9191```
9292
9393This example displays a memory allocation (Getmem is like the C malloc: it reserves memory for use), but we exit the execution without freeing the memory at the end of its use.
9494
9595#### Injections
9696
9797When 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 perform some action (e.g. delete some records/tables, 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).
9898
9999An SQL injection example:
100100
101101User Input:
102102
103-` Please enter your name: a' OR 1=1`
103+```text
104+ Please enter your name: a' OR 1=1
105+```
104106
105107Inside the code:
106108
107109``` pascal
108110...
109111Write('Please enter your name: ');
110112ReadLn(sName);
111113Query1.SQL.Add('SELECT Password FROM tblUsers WHERE Name='#32 + sName + #32);
112114...
113115```
114116
115117By submitting this SQL statement as user input, it will cause our query to add OR 1=1 to the SQL statement passed to the database: in this case, this always results in true, and the user has gained unauthorized access to the program.
116118
117119## Access to your data and modification
118120
119121Not only your program will have access to the data it uses. If you store the data in files or (remote) databases, an attacker can gain access through the operating system (and/or database and/or network layer/database protocol).
120122
121123### Encryption: is it enough?
122124
123125To counter the thread described above, programmers often use encryption. It can be used to provide:
124126
125127- protection of the confidentiality of data
126128- non-repudiation (is the data created by the person who said he did) and integrity (is the data unchanged) (using additional mechanisms such as digital signatures/hashing)
127129
128130both for
129131
130132- communication of data
131133- storing/retrieving data
132134
133135If you use these methods, your data is not automatically safe, however.
134136
135137There are multiple attacks possible on encrypted data:
136138
137139- attack on insecure algorithms or implementation (e.g. using a known plaintext attack)
138140- attack on the encryption keys (e.g. by reverse-engineering your program e.g. if it stores keys in a file or itself, or by patching the program so it intercepts encryption keys/passwords entered by the user).
139141
140142If you don't know exactly what you are doing (and you won't, unless you have cryptographic training), please use (in decreasing order of preference):
141143
142144- well known libraries that are maintained/patched (e.g. the built in FPC libraries, and libraries such as DCPCrypt, or external libraries like openssl or cryptlib) that use well known/widely used protocols that use well known/widely used algorithms
143145 - Use e.g. Trusted Authentication/SSPI for SQL Server/Firebird database connectivity to avoid sending passwords over the wire (Firebird 2 and lower: in clear text!) and rely on operating system security for authentication.
144146 - use cryptlib or openssl with Synapse to implement SSL/TLS instead of rolling your own solution
145147- well known/widely used protocols/APIs instead of rolling your own. These protocols must use well understood/widely used crypto/hashing algorithms. Examples:
146148 - GPG/PGP
147149 - TLS (SSL) with PKI/CAs (preferably don't trust only the CAs you need, and perform client certificate authentication if your threat analysis requires it)
148150 - SSH (e.g. with public/private key authentication, if necessary reinforced with passphrases for the keys)
149151 - on Windows, use the API to get the currently authenticated user. If you enforce proper OS security (password length, changes, physical access etc), you don't need to manage your own application leverl username/password mechanism.
150152- well understood crypto/hashing algorithms (such as AES/Rijndael, 3DES and SHA512). Be conservative in what algorithms you accept (e.g. MD5 is insecure for message signing).
151153
152154Encryption is part of a set of possible security measures; the effort/money spent on it should be evaluated as part of a seecurity analysis (see below).
153155
154156## Myths and Assumptions
155157
156158Many of the security issues exist because of ignoring important warnings and information that was given by the compiler, and by thinking that your program does not contain any exploitable problem.
157159
158160Here are some examples for this type of problem:
159161
160162### myths
161163
162164- Security by Obscurity – When no one knows about a problem no one can take advantage of it; e.g. use an obscure column name for storing passwords in your database.
163165- Secure programming language – There are languages such as Perl that many people think are secure from buffer overflows and other vulnerabilities while that is not true.
164166- Hash password is secure – A file that has a hashed password is not secure. Hash can only passed one and you can not retrieve the original data. *I don't get this. Does the author mean that a hashed password can be retrieved by a brute force or rainbow table attack and that it therefore needs a salt, or multiple hash rounds? --BigChimp 16:19, 24 July 2011 (CEST)*
165167- Nothing can break my program – Believing you're the only programmer in the world who writes faultless code is probably a bit optimistic. Maybe you're lucky and you just write code that doesn't work right without exploitable security vulnerabilies...
166168
167169### assumptions
168170
169171- The QA team will find and fix my security bugs.
170172- The user (or somebody else) will not attack my program and its data.
171173- My program will be used only for its original use.
172174- All exceptions can remain unhandled.
173175
174176## Analyse to understand threats and security
175177
176178In general, a programmer (or his employers/business owners) should perform an analysis of all threats (from physical access/attack through logical and social engineering attacks) should be done for the entire system (including the infrastructure – OS, database, network as well as physical machines/cabling/buildings/external connections) to analyse whether you're not leaving open a security hole that is unacceptable (from a risk/benefit perspective).
177179
178180The extent of the analysis should depend on the value of the data/processes that the system protects. It obviously makes no sense to go crazy trying to analyse your house's physical security when developing a hobby program to keep track of your bridge scores.
179181
180182Benefits of these kind of analyses:
181183
182184- risks remaining after security measures are made known or explicit. Often, there is some discussion on the chances of this risk occurring, or the impact associated with it, but the fact that there is a remaining attack vector is at least clear and decisions can be made based on that information
183185- you can fairly easily see what security measures are overengineered (“too secure”, waste of money) or underengineered (“not secure enough”). If you can't use this information now, you can at least learn from it for other projects, including future maintenance/modification of your program
184186
185187## Specific solutions
186188
187189Now we know some problems we can encounter when developing programs, we should learn how to fix these problems. All of the problems we saw above manifest into two types: assumptions and lack of careful programming. And for learning how to fix them, we first need to learn to think in a different way than we have up to now.
188190
189191### Overflow
190192
191193For 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.
192194
193195#### Buffer overflow
194196
195197If we return to our example of:
196198
197199``` pascal
198200var
199201 iNums: array [0..9] of Integer;
200202 ...
201203 FillChar(iNums[-1], 100, #0);
202204 ...
203205 for i := -10 to 10 do
204206 ReadLn(iNums[i]);
205207 ...
206208```
207209
208210We see here a range that was overflowed by our values, without even checking if the index number is correct.
209211
210212In 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.
211213
212214So the example should be changed into:
213215
214216``` pascal
215217var
216218 iNums: array [0..9] of Integer;
217219
218220 ...
219221 FillChar (iNums[Low(iNums)], High(iNums), #0);
220222 ...
221223
222224 for i := Low(iNums) to High(iNums) do
223225 ReadLn(iNums[i]);
224226 ...
225227```
226228
227229But wait, something is not right yet!
228230
229231The readln will accept an unlimited amount of chars, and no one promises us that it will be an integer or even in the range we can handle.
230232
231233#### Number Overflow
232234
233235While a string in Pascal is a pure array (hrmm hrmm.. not really, at least not in FPC, but let's pretend it is for a second, OK ?) so *readln* will try to find and see what are its limits and will not try to overflow the range we gave that type, but numbers are not the same.
234236
235237Numbers have limits, a computer/compiler has limits of many kinds regarding memory and numbers. It can give only a “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).
236238
237239In 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.
238240
239241So what can we do to fix that?
240242
241243First of all we may wish to work with a string variable that will be 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.
242244
243245For the first option we can do the following (copied from the FPC documentation):
244246
245247``` pascal
246248Program Example74;
247249
248250{ Program to demonstrate the Val function. }
249251Var
250252 I, Code: Integer;
251253begin
252254 Val(ParamStr(1), I, Code);
253255 If Code <> 0 then
254256 Writeln('Error at position ', code, ' : ', Paramstr(1)[Code])
255257 else
256258 Writeln('Value : ', I);
257259end.
258260```
259261
260262Here we see how to convert a string into an integer with some very easy error handling. The function *StrToInt* may also do the trick but it then we need to capture an exception in any error dealing.
261263
262264Here is a small example for a small *readln* like procedure for integer numbers.
263265
264266``` pascal
265267program MyReadln;
266268uses
267269 CRT;
268270
269271procedure MyIntReadLn(var Param: Integer; ParamLength: Integer);
270272var
271273 Line: string;
272274 ch: char;
273275 Error: Integer;
274276begin
275277 Line := '';
276278
277279 repeat
278280 ch := ReadKey;
279281 if (Length (Line) <> ParamLength) then
280282 begin
281283 if (ch in ['0'..'9']) then
282284 begin
283285 Line := Line + ch;
284286 write (ch);
285287 end
286288 else
287289 if (ch = '-') and (Length(Line) = 0) then
288290 begin
289291 Line := '-';
290292 write (ch);
291293 end;
292294 end;
293295
294296 if (ch = #8) and (Length(Line) <> 0) then // backspace
295297 begin
296298 Line := Copy(Line, 1, Length(Line) - 1);
297299 gotoxy(WhereX - 1, WhereY);
298300 write(' ');
299301 gotoxy(WhereX - 1, WhereY);
300302 end;
301303 until (ch = #13);
302304
303305 val(Line, Param, Error);
304306
305307 if (Error <> 0) then
306308 Param := 0;
307309
308310 writeln;
309311end;
310312
311313var
312314 Num : Integer;
313315begin
314316 Write('Number: ');
315317 MyIntReadLn(Num, 2);
316318 WriteLn('The number is: ', Num);
317319end.
318320```
319321
320322Please note that you can make it even better, and more efficient if you wish. This is only a very small example to show how to do it.
321323
322324#### What are the security risks in Overflows ?
323325
324326Overflow of memory can allow arbitrary CPU code to be executed and users may run whatever type of code they wish, and nothing can stop them.
325327
326328### Denial of Service
327329
328330Denial of Service (DoS) is one of the hardest types of attacks to prevent. The reasons are:
329331
330332- The denial of service can even be executed without any exploitable bug, like using the “ping” program on a lot of machines to DoS a machine connected to the internet.
331333- Every system resource can be a possible denial of service, like opening sockets, reading files or allocating memory.
332334- Removal of files like a kernel module can cause a big problem. *I don't get this line --BigChimp 19:32, 24 July 2011 (CEST)*
333335- Lack of configuration or wrong configuration can cause a denial of service as well if it allows vulnerable resources to be misused.
334336- Too much permissions or lack of them *I don't get that not enough permissions can be a problem. Neither too many, either? Is a security risk, obviously but not a DoS problem. --BigChimp 19:32, 24 July 2011 (CEST) --BigChimp 19:32, 24 July 2011 (CEST)*.
335337- Almost any type of exploit can result in a denial of service.
336338
337339So as you can see, a denial of service can be almost anything that can stop the system from working as it should, because of exploitation or buggy code or just a program that captures system resources..
338340
339341In the above denial of service example:
340342
341343``` pascal
342344procedure Recurse;
343345begin
344346 while (True) do
345347 begin
346348 Recurse;
347349 end;
348350end;
349351```
350352
351353I created also a stack overflow (another type of buffer overflow), that caused the computer to need more memory resources to continue executing the code.
352354
353355Any system resource that is available to the program can be abused by not returning it back to the system when the program “does not need it anymore”. The keeping of system resources like memory, or sockets remove from other programs the ability to perform some of their actions. That way most programs will stop their execution and report an error, and some will hang and keep on looking for the system resources.
354356
355357Please note that some of the abuse of system resources exists because of a bug in the programming, like waiting for a 150k buffer, while the actual buffer is only 2 bytes, and when the program is still looking for the 150k buffer a new request for a 150k buffer is made etc.. until the system is not able to answer any of the requests anymore (this is a known type of attack).
356358
357359A good workaround for this bug is to limit how many non full buffers can be allocated at one time. If the buffer is not full after a timeout, it should be free. However, this solution will also cause a Denial of Service, because the communication will stop anyway at some point, or a slow connection can cause data loss.
358360
359361### Injection
360362
361363There are many ways to inject some type of code into our programs. As we saw at the above example:
362364
363365User Input:
364366
365-` Please enter your name: a' OR 1=1`
367+```text
368+ Please enter your name: a' OR 1=1
369+```
366370
367371Inside the code:
368372
369373``` pascal
370374...
371375write('Please enter your name: ');
372376readln(sName);
373377Query1.SQL.Add('SELECT Password FROM tblUsers WHERE Name='#32 + sName + #32);
374378...
375379```
376380
377381The injection occurred when we do not filter our code (sanitize is the more professional word :)): this means checking that we receive only the exact type of input that we are looking for, and nothing else.
378382
379383For example, we could check if *sName* has spaces. If so, do not continue checking for the rest of the variable. This helps if the username is only allowed to be one word consisting of letters, maybe the tick sign (`'`) and maybe even underscore (`_`) and then it's over. If we enter a number, this should be illegal (unless we wish to use “hacker like language” (leetspeak), or allow the use of numbers.
380384
381385There are many ways to sanitize your data. The less effective one (but often used) is the following:
382386
383387#### Ineffective sanitising
384388
385389``` pascal
386390function ValidVar (const S: AnsiString; AllowChars: TCharset): Boolean;
387391var
388392 i: Word;
389393begin
390394 i := 0;
391395 Result := True;
392396
393397 while (Result) and (i <= Length(S)) do
394398 begin
395399 Inc(i);
396400 Result := S[i] in AllowChars;
397401 end;
398402end;
399403```
400404
401405The function return true if we have a valid structure of content given by the *AllowChars* in the *S* variable. Please note that this function is only a proof of concept and may need more work in order to be fully used.
402406
403407Another way to do the same is to use regular expression as the following (this is a Proof of concept only in the Perl language. FPC does not have a fully supported regular expression engine that allows to modify strings):
404408
405-`$sName =~ s/[^a-z0-9_\']//gi;`
409+```text
410+$sName =~ s/[^a-z0-9_\']//gi;
411+```
406412
407413The regular expression removes any non valid chars from the string and returns to us the purged string. Please note that as far as I know, this regular expression will work also in ereg engines, but with minimal adjustments (*g* flag instructs Perl to replace all the matching patterns found. *i* is for *case insensitivity*).
408414
409415Now when we know that our input is valid, we need to see what is the use of the variable content. If the variable content is going into a database, or a cgi script, or anything else that has its own syntax, we must escape the content according to the non-allowed or control characters of the relevant language (e.g. SQL for databases).
410416
411417There are many ways to escape this type of content. Let's assume for now that this content is going into a query of a database. Now first of all we must make sure that our escaping will not increase our data size above the length limits of our database fields. Because if they will, then we can change from an injection to a data loss/denial of server/buffer overflow problems (a respected database usually will truncate the data and sometimes not in a good location).
412418
413419Usually the only escaping we need to do for using a string in a database is to escape only the ticks (') char (although some databases may have problems with more chars then ticks). So all we should do is to represent ticks in a way that will not effect the database engine, like backslash tick (\\') or double every single tick to two ticks (''), or maybe even use another char that will be replace the ticks in the query and replace again when we will show it to the user.
414420
415421#### Restricting input by SQL query parameters
416422
417423Obligatory [visual illustration](https://xkcd.com/327/).
418424
419425After we made sure that we respect the limits, we can continue in our attempts. To escape the code we can use several approaches. A less debugging friendly way, but a sure way of correct escaping is to use the parameters technique:
420426
421427``` pascal
422428Query1.SQL.Add('SELECT Password FROM tblUsers WHERE Name=?');
423429Query1.Parameters.Add(sName);
424430if (Query1.Execute) then
425431...
426432```
427433
428434This technique allows the database engine to escape the parameter in a way that we could use the content without any problems of illegal characters. Also, some databases have increased performance for repeated calls to this code as it can prepare an internal statement with parameters for this. The down side is that we can never debug the outcome of the query. That is, we can not see how the content of *sName* embedded in the SQL statement, and we can never see if our query was correct because of that.
429435
430436However, once you have tested the query without parameters, adding the parameters is quite easy, so in practice, this problem is not as big as it seems.
431437
432438### Efficient code
433439
434440The security measures mentioned above complicate code if you used the most efficient code to get needed functionality. Fortunately, you can sometimes pass off code complications to a framework/library that deals with it. For example: in the SQL DoS example you can let the database engine deal with escaping data using parametrised queries at the small added cost of having to use parameters in your code.
435441
436442However, writing performance efficient but security-vulnerable code doesn't help anybody except liability lawyers, and forensic experts.
437443
438444## Beyond the document
439445
440446While in this document I gave some short (yeah, I know it's an understatement 😉) examples and information on how to create better code, there are many issues that I did not touch in this document. Part of them are user privileges for execution of the programs, system root kits and other problems that our code needs to take in consideration (environment variable is only one example).
441447
442448Please read more resources out there for security issues like
443449
444450Buffer Overflows:
445451
446452- <http://www.securiteam.com/securityreviews/5QP0L0AFFS.html>
447453- <http://www.securiteam.com/securityreviews/5OP0B006UQ.html>
448454- <http://www.securiteam.com/securityreviews/5IP0G1P8AM.html>
449455- <http://www.securiteam.com/securityreviews/6Q00L00BFI.html>
450456- <http://www.securiteam.com/securityreviews/5UP0B0A60Y.html>
451457
452458Denial Of Service:
453459
454460- <http://www.securiteam.com/securityreviews/5AP0V0AA1W.html>
455461- <http://www.securiteam.com/securityreviews/6Z00T0K95O.html>
456462
457463SQL Injection:
458464
459465- <http://www.securiteam.com/securityreviews/5DP0N1P76E.html>
460466- <http://www.securiteam.com/securityreviews/5UP010A6AA.html>
461467- <http://www.securiteam.com/securityreviews/5GP0E2K7FO.html>
462468
463469---
464470*From the Free Pascal wiki, page "Secure programming", as it stood on 2020-04-08 after a change by Jwdietrich, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
465471Category: Code

Back to history