To Build Your Code Quality (Chapter A): Secure Coding (Part 2)
In the previous part, we have mentioned to threats from Input Validation, Man-In-Middle, Session Management, Error Handling, and Logging & Outputs, there are four other items from the web application development Issue Group that a professional developer must be armed with is the knowledge of how to write his software in some capacity.
- Database Issues
- I/O & File Management Issues
- Memory Management Issues
Configuration Issues: In the chaos of software development, the consistent theme is that nothing consistent. It’s no matter what language we use, what platform we choose, we must understand some languages are susceptible to one kind of attack while others can be prone to other attacks. The platforms have the same problem. An application written in a particular language and run on a particular platform with different configurations are always a subject to be attacked. Configuration is understood from system perspective is all of the things that apply to the system and servers. It’s the software and operating systems the machines are running on. However, from the development perspective, it is a different definition. Here is referred to all of the options, feature flags, data elements that an application need to run itself, but they are optional configurations which are set up at startup or runtime. For example: sometimes, a developer wants to put a feature flag in a place for some reasons (it can be helpful for a tester to perform a given action), if the feature exists bugs, the system can be compromised. Likewise, if a developer exposes backing service URLs through configuration that can be modified by the user, or worse an attacker, he can trick the application into exposing information by simply changing the URL. Mitigating these risks is not very difficult. Separating versions of software and deliver them to the right people. This will reduce the risk of feature flags exposing software. Be careful what function we want to expose through configuration. If the configuration exposes where sensitive information or application information goes, or how it is exposed, reconsider how that configuration is set. Always pay attention to the data in the database such as preferences which acts as raw configuration flags. Finally, consider configuration at runtime, default configurations of the application server can expose too much info, we should use practices OWASP when configuring our runtime.
I/O & File Management Issues: File system is a great place for attackers to exploit because it usually contains helpful info for the attack. We have to put high attention on managing file systems and inputs & outputs. Especially, in DevOps, it becomes more and more complicated when many parties are working and sharing the file and inputs & outputs together. We must ensure that the application is running in the user process with controls. Only appropriate users are granted access to the file system that the application needs to run itself. Developers only should allow the application to read and to write the file system from a finite location and any process needed only has that same access. If the application needs to retrieve data from the file system, consider using the read-only file in these cases because the attackers love to modify the files that application needs to perform their own operations. With read-only files, attackers have less opportunity to do their intention. Another attacking vector is to use file upload. A popular example is that the application parses the file that a user uploads to perform its function. An attacker can inject malicious data that force data leakage, unexpected operations or simply cause crashes, making the system unavailable. The situation is worse if the contents are stored on a remote system or database. The best way that we have mentioned is to validate all user inputs. As a rule of the thumb is not trust any data in a file without inspection. The system allows file upload, so the attacker embeds some form of malware into that upload. The fix for this is as simple as providing a sandboxed area for intermediate uploads. The upload process should write the file and then trigger a malware scan. Once the file is determined ‘clean’, another process can move it to its final location, and so our theme continues.
Database Issues: Database is always attractive to all hackers. We have talked about SQL Injection in previous part when input validation is a good practice to mitigate this kind of attack. The other way to avoid this attack is that the framework and approach that we use to connect to the database. SQL statement should use bind variables and parameters. With this usage, we remove the risk of being susceptible to injection attacks. Using a bind variable allows us to place a variable into the statement where we expect the user’s input to be added. For example select * from account where username =” :username_p, then we execute the statement by passing a parameter for a bind variable from the user input (which is validated)
Often, the database connection string is used with plain text communication channel. We are at risk by an internal user connects with a database over this insecure channel. The proper way to connect to a database system is through secured channels. If not all databases, allow for encrypted communication between the client and the server.
Memory Management Issues: Memory management vulnerabilities are a very special class in that they’re only exposed to certain languages and certain situations. The most common issue is buffer-over-flow. A buffer overflow occurs when a program or process attempts to write more data to a fixed length block of memory, or buffer, than the buffer allocated to hold. In a high-level language, we seldom deal with raw memory allocations. Even in languages like Go, Java, and C#, where we have pointers, we’re not usually able to manipulate raw memory without dipping into unsafe code. It says that you are safe from these errors. However, as a professional developer, we encourage to understand how to deal with data structures in our code. With a low-level language like C/ C++, when we create an array and pointers to them, then the memory is allocated to them, we have to be responsible for managing this memory, checking the bounds of the array. Now, let’s see an example of the problem, assuming we declare 2 variables:
The first is 10 bytes for a character array
and the second is an unsigned integer for 4 bytes.
Array [A, B, C, D, E, F, G, H, I, J, 1, 2, 3, 4 ]
They happen to be next to each other in memory. Now your application asks for input and stores that value into the first array. We don’t expect more than 10 bytes characters in our response. However, we don’t check the length of the input. If an attacker writes a value that says 13 characters in size, it will overwrite the four bytes of the integer with the last one being a stop byte. Now, if your program then accesses the unsigned integer, and the attacker has done his or her homework, the program may execute other instruction sets often at the command of the attacker through user input or exception flows. Some exploitation can impact the stack, some impact the heap but both can be leveraged in specific cases and neither is pretty. There are some mitigations to deal with this risk. One is to randomize the memory allocations, so the likelihood of memory spaces touching is decreased but this is nowhere near perfect. The good way to protect against this attack is to use the three-fold approach.
- First is to use when available native protections in our development language
- Validate our inputs to ensure they are in acceptable bounds
- Finally, have a good testing strategy
Even if you aren’t writing code in assembly, C, C++ or Fortran, you may be leveraging components that are. You need to consider this whenever you write code that input and boundary checks are always important.