Внедрение SQL, в зависимости от типа используемой СУБД и условий внедрения, может дать возможность атакующему выполнить произвольный запрос к базе данных (например, прочитать содержимое любых таблиц, удалить, изменить или добавить данные), получить возможность чтения и/или записи локальных файлов и выполнения произвольных команд на атакуемом сервере.
Существует миф, что SQL Injection возможна только в Web-приложениях, но, к несчастью для ВСЕХ разработчиков, внедрение произвольного кода возможно в любом приложении где используется СУБД (так же любая).
В этой статье я хочу показать, что от внедрения SQL-кода не застрахован никто, в тои числе и профессиональные разработчики, пишушие крупные промышленные системы, которые успешно используются на предприятиях. В качестве “жертвы” выступит крупная система документооборота DocsVision 3.6. Опишу суть SQL Injection в DV: допустим у нас официально куплено N-лицензий этой системы документооборота, но мы решили увеличить кол-во этих лицензий путём внедрения своего произвольного кода. Изначально может показаться, что задача практически нерешаемая, так как это комерческий продукт и наверняка разработчики уделили достаточно много времени для его защиты, но…
Запускаем SQL Server Profiler и анализируем все запросы, которые генерит приложение DocsVision, особое внимание уделяем коду, который отсылает приложение при каждом новом подключении пользователя.
И вот, что удалось “поймать”:
1.
SELECT
COUNT
(*)
FROM
2.
(
SELECT
DISTINCT
[UserID], [ComputerName]
FROM
[dbo].[dvsys_sessions]) t0
Смотрим DDL-скрипт этой таблицы:
01.
CREATE
TABLE
[dbo].[dvsys_sessions](
02.
[SessionID] [uniqueidentifier] ROWGUIDCOL
NOT
NULL
,
03.
[UserID] [uniqueidentifier]
NULL
,
04.
[LocaleID] [
int
]
NOT
NULL
,
05.
[LoginTime] [
datetime
]
NOT
NULL
,
06.
[LastAccessTime] [
datetime
]
NOT
NULL
,
07.
[ComputerName] [
varchar
](32)
NULL
,
08.
CONSTRAINT
[PK_dvsys_sessions]
PRIMARY
KEY
CLUSTERED
09.
(
10.
[SessionID]
ASC
11.
)
WITH
(PAD_INDEX =
OFF
, STATISTICS_NORECOMPUTE =
OFF
, IGNORE_DUP_KEY =
OFF
, ALLOW_ROW_LOCKS =
ON
, ALLOW_PAGE_LOCKS =
ON
)
ON
[
PRIMARY
]
12.
)
ON
[
PRIMARY
]
13.
14.
GO
01.
CREATE
TABLE
[dbo].[dvsys_sessions](
02.
[SessionID] [uniqueidentifier] ROWGUIDCOL
NOT
NULL
,
03.
[UserID] [uniqueidentifier]
NULL
,
04.
[LocaleID] [
int
]
NOT
NULL
,
05.
[LoginTime] [
datetime
]
NOT
NULL
,
06.
[LastAccessTime] [
datetime
]
NOT
NULL
,
07.
[ComputerName] [
varchar
](32)
NULL
,
08.
[UserID2] [uniqueidentifier]
NULL
,
09.
[ComputerName2] [
varchar
](32)
NULL
,
10.
CONSTRAINT
[PK_dvsys_sessions]
PRIMARY
KEY
CLUSTERED
11.
(
12.
[SessionID]
ASC
13.
)
WITH
(PAD_INDEX =
OFF
, STATISTICS_NORECOMPUTE =
OFF
, IGNORE_DUP_KEY =
OFF
, ALLOW_ROW_LOCKS =
ON
, ALLOW_PAGE_LOCKS =
ON
)
ON
[
PRIMARY
]
14.
)
ON
[
PRIMARY
]
15.
16.
GO
01.
CREATE
TRIGGER
[dbo].[FuckOffConnections]
ON
[dbo].[dvsys_sessions]
02.
FOR
INSERT
03.
AS
04.
Update
t1
05.
set
06.
UserID=
null
,
07.
ComputerName=
null
,
08.
UserID2=t2.UserID,
09.
ComputerName2=t2.ComputerName
10.
From
dvsys_sessions t1
inner
join
inserted t2
on
t1.SessionID=t2.SessionID
Но на этом наше SQL-внедрение не закончено, ведь на эту таблицу могут ссылаться другие объекты базы данных, что может привести к сбою в работе. Для поиска всех объектов, которые ссылаются на таблицу [dbo].[dvsys_sessions], выполним запрос:
1.
select
distinct
OBJECT_NAME(id) obj
2.
from
sys.sysdepends
3.
where
depid=
OBJECT_ID
(
'dbo.dvsys_sessions'
)
В результате мы получили 4 объекта:
1.
dvsys_log_write_message
2.
dvsys_session_get_info
3.
dvsys_session_list
4.
FuckOffConnections
На деле изменения затронули всего 3 объекта, причём всего пару строк:
dvsys_log_write_message
001.
ALTER
PROCEDURE
[dbo].[dvsys_log_write_message] (
002.
@UserID
AS
uniqueidentifier,
003.
@SessionID
AS
uniqueidentifier,
004.
@
Type
AS
int
,
005.
@Operation
As
int
,
006.
@Code
AS
int
,
007.
@TypeID
AS
uniqueidentifier =
NULL
,
008.
@ResourceID
AS
uniqueidentifier =
NULL
,
009.
@ParentID
AS
uniqueidentifier =
NULL
,
010.
@NewResourceID
AS
uniqueidentifier =
NULL
,
011.
@ResourceName
As
nvarchar
(512) =
NULL
,
012.
@Data
AS
ntext =
NULL
013.
)
014.
AS
015.
BEGIN
016.
SET
NOCOUNT
ON
017.
DECLARE
@ComputerName
AS
varchar
(32)
018.
DECLARE
@InternalData
AS
varchar
(128)
019.
SELECT
@InternalData =
NULL
020.
021.
-- Retrieve ComputerName
022.
--Оригинальный код
023.
--SELECT @ComputerName = [ComputerName]
024.
--FROM [dbo].[dvsys_sessions] WITH(NOLOCK)
025.
--WHERE [SessionID] = @SessionID
026.
027.
--Код, который мы внедрили:
028.
SELECT
@ComputerName = [ComputerName2]
029.
FROM
[dbo].[dvsys_sessions]
WITH
(NOLOCK)
030.
WHERE
[SessionID] = @SessionID
031.
032.
-- Retrieve LoginTime for Logout operation
033.
IF @Operation = 2
034.
BEGIN
035.
SELECT
@InternalData =
CONVERT
(
varchar
(128), [LoginTime], 20)
036.
FROM
[dbo].[dvsys_sessions]
WITH
(NOLOCK)
037.
WHERE
[SessionID] = @SessionID
038.
END
ELSE
039.
-- Get Card description and Card type ID
040.
IF (@Operation >= 3
AND
@Operation <= 7)
OR
@Operation = 20
041.
BEGIN
042.
SELECT
@ResourceName = [Description], @TypeID = CardTypeID
043.
FROM
[dbo].[dvview_instances]
WITH
(NOLOCK)
044.
WHERE
InstanceID = @ResourceID
045.
END
ELSE
046.
-- Get Card description, Card type ID and topic name
047.
IF @Operation = 27
048.
BEGIN
049.
SELECT
@ResourceName = [Description], @TypeID = CardTypeID, @InternalData = [Topic]
050.
FROM
[dbo].[dvview_instances]
WITH
(NOLOCK)
051.
WHERE
InstanceID = @ResourceID
052.
END
ELSE
053.
-- Get InstanceID and description of Card that has row with specified ResourceID in section with specified TypeID
054.
IF (@Operation >= 8
AND
@Operation <= 12)
OR
@Operation = 13
OR
@Operation = 19
055.
BEGIN
056.
IF @ParentID
IS
NULL
057.
BEGIN
058.
DECLARE
@GetRowParentID
AS
nvarchar
(256)
059.
SELECT
@GetRowParentID = N
'SELECT @ParentID = [InstanceID] FROM [dvtable_{'
+
CONVERT
(
nvarchar
(36), @TypeID) +
060.
N
'}] WITH(NOLOCK) WHERE [RowID] = '
'{'
+
CONVERT
(
nvarchar
(36), @ResourceID) + N
'}'
''
061.
062.
EXECUTE
[dbo].[sp_executesql] @GetRowParentID, N
'@ParentID uniqueidentifier OUTPUT'
, @ParentID
OUTPUT
063.
END
064.
065.
SELECT
@ResourceName = [Description]
066.
FROM
[dbo].[dvview_instances]
WITH
(NOLOCK)
067.
WHERE
InstanceID = @ParentID
068.
END
ELSE
069.
-- Get File name
070.
IF ((@Operation >= 14)
AND
(@Operation <= 18))
OR
(@Operation = 21)
OR
(@Operation = 24)
OR
(@Operation = 25)
071.
BEGIN
072.
SELECT
@ResourceName = [
Name
]
073.
FROM
[dbo].[dvview_files]
WITH
(NOLOCK)
074.
WHERE
FileID = @ResourceID
075.
076.
-- Get Destination File name
077.
IF (@Operation = 25)
078.
BEGIN
079.
SELECT
@InternalData = [
Name
]
080.
FROM
[dbo].[dvview_files]
WITH
(NOLOCK)
081.
WHERE
FileID = @ParentID
082.
END
083.
END
ELSE
084.
-- Get Card description
085.
IF @Operation = 19
086.
BEGIN
087.
SELECT
@ResourceName = [Description]
088.
FROM
[dbo].[dvview_instances]
WITH
(NOLOCK)
089.
WHERE
InstanceID = @ParentID
090.
END
ELSE
091.
-- Get Report alias
092.
IF @Operation = 23
093.
BEGIN
094.
SELECT
@ResourceName = [Alias]
095.
FROM
[dbo].[dvsys_reports]
WITH
(NOLOCK)
096.
WHERE
ID = @ResourceID
097.
END
ELSE
098.
-- Get Card Type alias
099.
IF @Operation = 26
100.
BEGIN
101.
SELECT
@ResourceName = [Alias]
102.
FROM
[dbo].[dvsys_carddefs]
WITH
(NOLOCK)
103.
WHERE
CardTypeID = @ResourceID
104.
END
105.
106.
INSERT
[dbo].[dvsys_log]
WITH
(ROWLOCK) (UserID, ComputerName, [
Date
],
Type
, Operation, Code, TypeID, ResourceID, ParentID, ResourceName, NewResourceID, Data)
107.
VALUES
(@UserID, @ComputerName,
GETDATE
(), @
Type
, @Operation, @Code, @TypeID, @ResourceID, @ParentID, @ResourceName, @NewResourceID,
ISNULL
(@Data, @InternalData))
108.
END
01.
ALTER
PROCEDURE
[dbo].[dvsys_session_get_info] (
02.
@SessionID
AS
uniqueidentifier
03.
)
04.
AS
05.
BEGIN
06.
--Оригинальный код
07.
-- SELECT t0.UserID, LocaleID, AccountName, ISNULL(ComputerName, '')
08.
-- FROM [dbo].[dvsys_sessions] t0 WITH(NOLOCK)
09.
-- JOIN [dbo].[dvsys_users] t1 WITH(NOLOCK) ON t0.UserID = t1.UserID
10.
-- WHERE (SessionID = @SessionID)
11.
12.
--Внедрённый код
13.
SELECT
t0.UserID2
as
UserID, LocaleID, AccountName,
ISNULL
(ComputerName2,
''
)
14.
FROM
[dbo].[dvsys_sessions] t0
WITH
(NOLOCK)
15.
JOIN
[dbo].[dvsys_users] t1
WITH
(NOLOCK)
ON
t0.UserID2 = t1.UserID
16.
WHERE
(SessionID = @SessionID)
17.
END
01.
ALTER
PROCEDURE
[dbo].[dvsys_session_list]
02.
AS
03.
BEGIN
04.
05.
--Оригинальный код
06.
-- SELECT SessionID, AccountName, LoginTime, LastAccessTime, ComputerName
07.
-- FROM [dbo].[dvsys_sessions] t0 WITH(NOLOCK)
08.
-- JOIN [dbo].[dvsys_users] t1 WITH(NOLOCK) ON t0.UserID = t1.UserID
09.
-- ORDER BY AccountName ASC, LoginTime DESC
10.
11.
--Внедрённый код
12.
SELECT
SessionID, AccountName, LoginTime, LastAccessTime, ComputerName2
as
ComputerName
13.
FROM
[dbo].[dvsys_sessions] t0
WITH
(NOLOCK)
14.
JOIN
[dbo].[dvsys_users] t1
WITH
(NOLOCK)
ON
t0.UserID2 = t1.UserID
15.
ORDER
BY
AccountName
ASC
, LoginTime
DESC
16.
END