transaction ; 트랜잭션

컴퓨터 프로그램에서 트랜잭션의 일반적인 의미는 정보의 교환이나 데이터베이스 갱신 등 연관되는 작업들에 대한 일련의 연속을 의미하는데, 데이터베이스의 무결성이 보장되는 상태에서 요청된 작업을 완수하기 위한 작업의 기본 단위로 간주된다.

전형적인 트랜잭션의 예로, 고객의 전화 주문을 받아 대리인이 주문내용을 컴퓨터에 입력하는 것을 들 수 있는데, 이 주문 트랜잭션은 다음과 같은 여러 개의 작업단계로 이루어진다.

  • 데이터베이스로부터 재고량 조사하기
  • 그 상품이 가용한지(혹시, 다른 고객으로부터 예약된 것인지의 여부) 확인하기
  • 주문하기
  • 주문이 이루어졌는지 확인하기
  • 예상 선적시간 확인하기

위의 작업단계를 하나의 트랜잭션으로 보았을 때, 트랜잭션이 성공적으로 끝나기 위해서는 각 작업 단계들이 모두 완성되어야만 하며, 그랬을경우 비로소 이 새로운 주문 내용이 데이터베이스에 실제로 반영된다. 만약 그렇지 못했을 경우, 즉 어떤 한 작업 단계에서라도 오류가 발생하면 데이터베이스에는 아무런 수정이 이루어지지 않으며, 트랜잭션이 시작되기 이전 상태로 유지된다. 트랜잭션이 성공리에 끝났을때 이루어지는 데이터베이스의 갱신을 "commit" 이라고 부르며, 트랜잭션이 실패되었을때 데이터베이스의 수정 내용이 취소되는 것을 "rollback"이라고 부른다. 트랜잭션의 각 사건들을 관리 감독하는 프로그램을 트랜잭션 모니터라고 하며, 트랜잭션은 SQL에 의해 제공된다.


가끔 어떤 컴퓨터에서 트랜잭션이라는 용어는 다른 의미를 갖는다. 예를 들어 IBM 대형기종 운영체계의 배치 처리에서, 트랜잭션은 작업(job) 또는 작업단계(job step)를 의미한다.


출처 - terms.co.kr






[Spring] TransactionManager와 DataSource


들어가며


Spring에서 트랜잭션을 적용하는 방법은 여러가지가 있지만, 보통 선언적 트랜잭션(declarative transaction)을 많이 사용한다. 필자도 XML 설정을 통해 어플리케이션의 상태를 변경하도록 하는 선언적 접근방식을 좋아하는 편이라 선언적 트랜잭션을 사용하고 있다. 스프링에서의 트랜잭션 적용 방법에 대해서는 여타 다른 기본서들이나 spring 문서에도 잘 나와있으므로 여기에서 설명하지는 않는다. 이 글에서는 필자의 경험을 예로 들어 트랜잭션 매니저의 역할에 대한 이해를 돕고, Propagation에 대해 간략하게 설명하도록 한다.



스프링 TransactionManager

스프링에서는 다양한 TransactionManager를 제공하는데 보통 일반적인 웹어플리케이션에서는 DataSourceTransactionManager를 많이 사용할 것이다. 

(사실 스프링에서 사용하는 트랜잭션 매니저의 동작을 이해하기란 쉽지 않다. 아래 그다지 간단하지 않은 트랜잭션 설정을 예제로 포함했지만, 트랜잭션 적용 설정을 설명하고자 함이 아니기 때문에 아래 설정 코드는 굳이 볼 필요는 없다.)

접기

    <!-- Data Source -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName">
            <value>${dataSource.driverClassName}</value>
        </property>
        <property name="url">
            <value>${dataSource.url}</value>
        </property>
        <property name="username">
            <value>${dataSource.username}</value>
        </property>
        <property name="password">
            <value>${dataSource.password}</value>
        </property>
        <property name="maxActive">
            <value>${dataSource.maxActive}</value>
        </property>
        <property name="maxIdle">
            <value>${dataSource.maxIdle}</value>
        </property>
        <property name="maxWait">
            <value>${dataSource.maxWait}</value>
        </property>
        <property name="defaultAutoCommit">
            <value>true</value>
        </property>
        <property name="validationQuery">
            <value>${dataSource.validationQuery}</value>
        </property>
        <property name="testOnBorrow">
            <value>${dataSource.testOnBorrow}</value>
        </property>
        <property name="testWhileIdle">
            <value>${dataSource.testWhileIdle}</value>
        </property>
        <property name="timeBetweenEvictionRunsMillis">
            <value>${dataSource.timeBetweenEvictionRunsMillis}</value>
        </property>
        <property name="minEvictableIdleTimeMillis">
            <value>${dataSource.minEvictableIdleTimeMillis}</value>
        </property>
    </bean>
    
    <!-- SqlMapClient -->
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="configLocation" value="/WEB-INF/conf/ibatis/sqlmap-config.xml" />
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
        <property name="sqlMapClient" ref="sqlMapClient" />
    </bean>
    
    <!-- TrasactionManager -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- auto name proxy for DataManager Transaction -->
    <bean id="nameMatchAS"class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
        <property name="properties">
            <props>
                <prop key="*">PROPAGATION_REQUIRED,
                    -RuntimeException, -Exception</prop>
            </props>
        </property>
    </bean>

    <!-- TransactionInterceptor -->
    <bean id="transactionInterceptor"class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager">
            <ref bean="transactionManager" />
        </property>
        <property name="transactionAttributeSource">
            <ref bean="nameMatchAS" />
        </property>
    </bean>
    <bean id="autoProxyCreator"class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="interceptorNames">
            <list>
                <idref bean="transactionInterceptor" />
            </list>
        </property>
        <property name="beanNames">
            <value>*Service</value>
        </property>
    </bean>

보기만해도 어지럽다. 스프링 설정의 큰 특징은 객체간의 의존관계가 명확하게 드러난다는 점이다. 물론 모든 경우가 다 그런것을 아니지만 일반적으로 빈 A가 빈 B의 parent로 설정되어 있다면 B는 A를 상속한 클래스일 것이다. 

하지만 위의 설정은 객체간의 관계를 전혀 알 수 없다. 대표적으로 실제로 SQL구분을 수행하는 매퍼인 iBatis의 sqlMapClient는 DataSource를 참조하고 있고, TransactionManager 설정도 DataSource를 참조하고 있다.

접기



보통 트랜잭션 설정을 하게 되면 DataSource를 선언하고 DataSourceTransactionManager를 선언하게 된다. 그리고 트랜잭션 매니저를 AOP 방식으로 특정 빈의 메소드에 반영하도록 하는 설정이 일반적이다.

그렇다면 트랜잭션 매니저를 사용할 때 DataSource (또는 Connection)의 defaultAutoCommit (또는 autoCommit) 설정을 false로 해놓아야 할까?


DataSourceTransactionManager의 내부를 보면 다음과 같은 코드를 발견할 수 있다.

접기

protected void doBegin(Object transaction, TransactionDefinition definition) {
    ....
    // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
    // so we don't want to do it unnecessarily (for example if we've explicitly
    // configured the connection pool to set it already).
    if (con.getAutoCommit()) {
        txObject.setMustRestoreAutoCommit(true);
        if (logger.isDebugEnabled()) {
            logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
        }
        con.setAutoCommit(false);
    }
    ...

접기


위 코드에서도 알 수 있듯이, 트랜잭션 매니저 내부에서 Connection의 autoCommit 설정이 true로 넘어오더라도 강제로 false로 세팅한다. 따라서 DataSource에서 defaultAutoCommit 설정을 true로 놓더라도 별다른 문제는 생기지 않는다. 

오히려 DataSource의 defaultAutoCommit 설정은 true로 하는 것이 좋다. 어차피 DataSource를 커넥션 풀의 역할을 할 뿐이지 트랜잭션 관리의 책임까지 지지 않기 때문이다. 무슨 말이나면, DataSource에서 직접 Connection을 얻어서 사용할 때(이런 과정을 커넥션을 Fetch한다고 한다.) autoCommit을 false로 준다고 하더라도 나중에 커넥션이 반환되었을때 DataSource는 commit/rollback 처리를 보장하지 않는다는 말이다. 즉, 커넥션을 얻어서 트랜잭션을 시작할지, 커밋을할지 롤백을할지에 대한 책임은 커넥션을 사용하는 어플리케이션에게 있다.

트랜잭션 매니저와 DataSource의 역할을 이해한다면, 왜 트랜잭션 매니저 내부에서 강제로 커넥션의 autoCommit 을 false로 세팅하는지 이해할 수 있을 것이다.



스프링 Propagation

그렇다면 Propagation은 무엇인가? 바로 트랜잭션의 전파(propagation) 규칙을 정의하는 것이다. 
사실 트랜잭션의 전파라는 말 자체가 상당히 이해하기 힘들다. 스프링에서 제공하는 트랜잭션 전파 규칙들을 살펴보자.

접기

- PROPAGATION_MADATORY
  : 반드시 트랜잭션 내에서 메소드가 실행되야 함
  : 트랜잭션이 없는 경우에는 예외 발생
 
- PROPAGATION_NESTED
  : 트랜잭션에 있는 경우, 기존 트랜잭션 내의 nested transaction 형태로 메소드 실행
  : nested transaction 자체적으로 commit, rollback 가능
  : 트랜잭션이 없는 경우, PROPAGATION_REQUIRED 속성으로 행동
 
- PROPAGATION_NEVER
  : 트랜잭션 컨텍스트가 없이 실행되야 함
  : 트랜잭션이 있으면 예외 발생
 
- PROPAGATION_NOT_SUPPORTED
  : 트랜잭션이 없이 메소드 실행
  : 기존의 트랜잭션이 있는 경우에는 이 트랜잭션을 호출된 메소드가 끝날때까지 잠시 보류
  : JTATransactionManager를 사용하는 경우에는, TransactionManager가 필요
 
- PROPAGATION_REQUIRED
  : 트랜잭션 컨텍스트 내에서 메소드가 실행되야 함
  : 기존 트랜잭션이 있는 경우, 기존 트랜잭션 내에서 실행
  : 기존 트랜잭션이 없는 경우, 새로운 트랜잭션 생성
 
- PROPAGATION_REQUIRED_NEW
  : 호출되는 메소드는 자신 만의 트랜잭션을 가지고 실행
  : 기존의 트랜잭션들은 보류됨
  : JTATransactionManager를 사용하는 경우에는, TransactionManager가 필요
 
- PROPAGATION_SUPPORTS
  : 새로운 트랜잭션을 필요로 하지는 않지만, 기존의 트랜잭션이 있는 경우에는 트랜잭션 내에서 실행

접기


사실 스프링에서의 트랜잭션 전파란 여러 메소드(실행)의 수행을 하나의 트랜잭션(작업)으로 묶기 위한 규칙을 말한다. 즉, 어떠한 작업이 A, B, C 메소드의 실행으로 이루어져 있다면, 이 3가지 메소드를 하나의 트랜잭션으로 묶을 것인지, B 메소드만 별도의 트랜잭션으로 분리할 것인지 등등에 대한 설정을 제공하는 것이다. 
http://static.springsource.org/spring/docs/2.5.x/reference/transaction.html#tx-propagation
(스프링에서는 메소드 단위로 트랜잭션 Propagation 설정을 할 수 있다.)



트랜잭션 매니저는 사용하면서 autocommit 은 true로 할 수 없는가?

이 글의 핵심이자 위 내용의 응용이다. 보통은 한번의 request로 인해 발생하는 일련의 수행들을 하나의 트랜잭션으로 묶는것이 일반적이지만, 그러고 싶지 않은 경우도 분명히 존재한다. 

'어플리케이션에서는 하나의 트랜잭션이지만 실제 DB작업은 트랜잭션을 걸지 않고 처리'하고 싶은 경우도 있다. 예를 들어 로그 데이터와 같은 경우, 일련의 로그를 쌓던 도중 에러가 났다고 해서 모두 롤백이 되어 버리면 로그의 의미가 없어지게 되므로 개별 로그 데이터를 쌓는 작업을 별도의 트랜잭션으로 설정해야 한다.

이런 경우 DataSource를 직접 사용하면 되지만 만약 여러개의 쓰레드가 많은 양의 CRUD 작업을 하게 되면, 한번의 sql 처리에 DataSource로부터 커넥션 Fetch와 Return이 반복되어 발생하며 여러 쓰레드가 동시에 이러한 작업을 하게 되므로, DataSouce가 병목지점이 될 수 있다. 

이러한 현상을 피하기 위해서 하나의 트랜잭션에 대해서 하나의 커넥션만 fetch하도록 해야 하며 이와 동시에 커넥션의 autocommit이 true여야 한다는 요구조건을 만족시켜야 한다.

이런 경우 해당 처리 메소드들을 트랜잭션에 참여시키지 않도록 하면 간단하게 요구사항을 만족시킬 수 있다.

접기

    ......
    <!-- auto name proxy for DataManager Transaction -->

    <bean id="nameMatchAS"class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
        <property name="properties">
            <props>
                <prop key="*">PROPAGATION_NOT_SUPPORTED,
                    -RuntimeException, -Exception</prop>
            </props>
        </property>
    </bean>
    ......

접기


이렇게 하면 DB에서 트랜잭션이 발생하지 않으면서 (autocommit = true) TransactionManager에 의해 커넥션을 한번만 fetching 하게 된다.


출처 - http://tinywolf.tistory.com/107


===================================================================================


<?xml version="1.0" encoding="UTF-8"?>

<beans default-autowire="no" default-lazy-init="false"
 default-dependency-check="none"
 xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:jee="http://www.springframework.org/schema/jee"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">

 <!-- DB 설정파일 위치 지정 -->
 <bean id="dbConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location"><value>WEB-INF/config/db.properties</value></property>
 </bean>

    <!-- dataSource 정의 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
        <property name="url"><value>${jdbc.url}</value></property>
        <property name="username"><value>${jdbc.username}</value></property>
        <property name="password"><value>${jdbc.password}</value></property>
        <property name="maxActive"><value>${jdbc.maxActive}</value></property>
        <property name="maxIdle"><value>${jdbc.maxIdle}</value></property>
        <property name="maxWait"><value>${jdbc.maxWait}</value></property>
    </bean> 
 
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 
     <property name="dataSource" ref="dataSource" />
     <property name="configLocation" value="WEB-INF/config/sqlMapConfig.xml" />
    </bean>     
    
    <bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
 </bean>
 
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
     <tx:attributes>
      <tx:method name="execute*" read-only="false" rollback-for="BizException" propagation="REQUIRED"/>
     </tx:attributes>
    </tx:advice>
    
    <aop:config>
     <aop:pointcut id="testServiceProcOperation" expression="execution(* test.logic.proc.*Proc.*(..))" />
     <aop:advisor advice-ref="txAdvice" pointcut-ref="testServiceProcOperation"/>
    </aop:config>
</beans>


출처 - http://bbaeggar.tistory.com/86











Posted by linuxism
,

로그인에 관련된 보안얘기를 하려고 합니다.

password(); // mysql.
md5(); // php.
crypt(); // php.

뭐, 암호화에 관련된 함수들이 여러 가지 있겠지만 위 3가지 함수는 범용적으로 많이들 쓰고 있고
안정성이 검증된 함수들이죠.. 그리고 모두 복호화가 안되거나, 어려운 해쉬함수들입니다.

흔히 password() 로 암호화시킨 비밀번호... 원래의 값을 절대 알 수 없다고들 표현합니다......

절대 알 수 없다 ?
절대 알 수 없다 ?
절대 알 수 없다 ?

id = 'abcd'
pw = '4ed0bdda4ee8f6a5'

위 pw 원래의 값을 과연 절대 알 수 없을까요 ? 정말 그럴까요 ?


password() 뿐 아니라, md5(), crypt() 등 해쉬함수들이 있는데요....
지금부터 제가 생각하고 있는 바를 차근차근 얘기해 나가려고 합니다....

그다지 어려운얘기 아니고요,

누구나 알 수 있는 쉬운 얘기들뿐입니다..

( 제가 워낙 쉬운 것만 좋아해서 쉬운 것밖에는 모릅니다 ^^ )


--------------------------------------------------------------------------------------


가령,

사용자가 비밀번호를 '3204' 라고 입력한 것을  md5() 로 해쉬시키면

' 640258597cbc50037072712f964cf5d8 ' 라는 값이 나오게 되지요...

md5() 는 디코딩을 지원하지 않는 단방향성 해쉬함수이기에 위 문자열을 보고 원래 값 '3204'를
추출 할 수는 없을 것입니다.

나머지 password(), crypt() 들도 마찬가지죠.. 상당히 어려운 알고리즘을 통해 해쉬된 문자열이
나오면서 원래의 값을 알아낼 수는 없게 됩니다.

특히 crypt() 는 두 번째에 주어지는 salt 값에 따라  똑같은 패스워드를 해쉬해도 결과는
매번 틀리게 됩니다...

가령,

$password = '20930';
$savepw = crypt( $password, md5( time() ) );
이런식으로 하면

실행 할 때마다 94JrlBxXigCZU , 30K4887zrFHBw , 등의 틀린 결과를 나오게 합니다.

모두 대단한 함수들입죠 ~~


------------------------- 그런데, 취약점은 항상 있는 법 --------------------------------

아이디 해킹을 하기위한 몇 가지 조건들이 있기 마련이죠..
그중, 해킹에 의해 DB가 노출되었다고 칩니다......

DB 비번관리를 잘못하던지, 디렉토리파싱을 당하던지 여러 이유로 DB가 노출되었다고 치구요..

DB 내용에는 사용자 계정 정보가

id = 'abcd'
pw = '640258597cbc50037072712f964cf5d8'

위처럼 되있다고 치구요.....


비밀번호가 암호화 되어있으니, 과연 안전할까요 ?

그런데 위 문자열 패턴을 보면, md5 로 해쉬된 것을 쉽게 알 수 있습니다.
지금부터 md5() 함수를 이용해서 원래의 비번을 찾아보도록 하겠습니다.

<?
for( $pw = 0; $pw < 99999; $pw ++ ) {
       if( '640258597cbc50037072712f964cf5d8' == md5( $pw ) ) {
              echo "find ok : $pw ";
              break;
       }
}
// 출력 -> find ok : 3204
?>

간단하게 찾았습니다.



그러면, crypt() 는 안전 할까요 ??

아닙니다.. crypt() 역시 똑 같습니다..

노출된 DB 에.
id = 'abcd'
pw = '12FP8QJo.OCVg';

위 문자열 패턴을 보면, crypt() 로 해쉬된 것을 알 수 있습니다.
이것도 원래의 비번을 찾아보도록 하겠습니다.
<?
for( $i = 0; $i < 99999; $i ++ ) {
       if( '12FP8QJo.OCVg' == crypt( $i, '12FP8QJo.OCVg' ) ) {
              echo " find ok : $i ";
              break;
       }
}
// 출력 -> find ok : 20930
?>

이번에도 결과가 나와 버립니다.


하지만 실제 암호는 숫자만 쓰는 것이 아니고, 문자열을 섞어서 입력하겠지요?
그렇다고 못 찾는 건 아닙니다.
.....
.....
$pw = $chk.chr($i);
if( '640258597cbc50037072712f964cf5d8' == md5( $pw ) ) {
.....
.....
}
$i ++;
if( $i > 126 ) $chk = ( ord( $chk ) + 1 );
.....
.....

저런 식으로 숫자 대신 문자를 자동으로 대입하게 해서 돌아가게 만들면 된다는 것이죠....

수행시간이 오래 걸린 것입니다만,,
문자 하나당 유효값이 93 자 인가? 그러니, 비밀번호가 4글자만 되도
loop 를 93 * 93 * 93 * 93 = 74,805,201 번을 돌아야 할 것입니다...

문제는 요즘 컴퓨터 성능이 워낙 좋아져서, loop 도는 시간이 그리 오래 걸리지도 않고,
또, 단위별로 끊어서 몇 단계 나누어서 병렬로 돌리면 결과는 더욱 빨리 나오게 된다는 것이죠.

그리고, 시간이 좀 걸려서 1주일쯤 걸렸다고 칩니다.
그 1주일동안 해커가 컴퓨터 앞에 매달려 있습니까 ? 잠을 못잡니까 ?
계속 지켜봐야 합니까 ?? 아니면 컴퓨터가 망가집니까 ?

그냥 디코드 돌려놓고 지 할일 하다가 결과 나오면 그때 해킹하는 것이죠...



------------------------------------------------------------------------------

결국은 DB 가 노출되거나, 해쉬된 비밀번호의 결과값이 노출되면 게임은 끝이라는 겁니다.

가령, 비밀번호를 암호화시켰다고 해서 비밀번호를 쿠키로 날아다니게한다던지,
페이지에 폼값으로 날려준다던지 하는 것은 해커에게 비밀번호를 다이렉트로 알려주는 것과
같습니다......

그럼,

비밀번호 안 날라 다니게 하고 DB노출만 막으면 안전할까요 ??


그것도 만만치 않습니다.

크라이언트에서 서버 접속해서 비밀번호 처음에 날려줄 때,,,,
네트웍을 감시당하면 아예, 원 비밀번호가 그대로 노출될 수 있습니다...

그래서 클라이언트에서도 JavaScript 를 이용해서 나름대로 암호화시킨 문자열을
날아다니게 하지요...

네트웍 감시를 당했을 때, 암호화된 비밀번호가 노출되는 것이구요..

하지만,, 이 역시 결과는 마찬가지죠.......

이 경우는 암호를 풀고 자시고도 없이
그냥 암호화된 비번 그 자체를 login 페이지로 날려서 그대로 접속해 버리면 되지요..
이 경우라면 원래의 암호는 알 필요도 없는 것이지요..


-------------------- 그럼 가장 안전한 방법은 무엇인가 ---------------------------------

개발자는 어떤 상황이던 비밀번호 스트링이 네트웍감시를 당하던, DB 가 노출되던 해서
해커에게 알려진다고 간주해야 합니다.

그럼 이러던 저러던 끝이라는 얘기일까요 ?
... 꼭 그렇지만은 않다고 봅니다.

여기서 우리는 보안을 위해 몇 가지 노력해야 할 것이 있습니다.


#-------------- 비밀번호가 어떤 모듈로 해쉬가 됐는지 모르게 하자.

가장 쉬운 방법은,, 암호화함수 1개에 의존하지 않고 여러 함수를 거쳐 2중 3중으로 돌려야 합니다.



#-------------- 외부에 노출이 안되는 서버만의 고유한 토큰키를 만들자.

가령,

사용자가 '1234' 라는 비밀번호를 쳤을 때 DB 저장할 때는 고유의 '키값'을 정해 눌러서 저장합니다.
예) 키값 '1011' + 비밀번호 '1234' = '2245'
저장도 '2245' 로 저장하고 로그인 인증 때도 또 키값을 더해서 인증하는 것이죠...

'키값'은 절대 DB 에 저장하지 않고, 파일로 접근하게 만듭니다.
서버 자체가 해킹당하기 전에는 절대 볼 수 없도록 실행권한과 접근권한도 설정 되야 겠지요..



#-------------- 클라이언트와 주고받는 비밀번호역시 또 다른 유일한 '키값'을 부여해서 주고받습니다...

서버쪽에서 DB 저장할 때와는 조금 틀리죠...
이 '키값'은 접속시마다 틀리게 합니다.

예) 키값 '0012'

클라이언트 '1234' + '0012' = '1246' ---- 송신 ----> 서버 '1246' - '0012' = '1234' 저장.

그런데,
주고 받을때 사용하는 키값은 클라이언트 페이지에 남아 있으면 안됩니다.

가령,

<form name=hashKey type=hidden value='<?=$hashKey?>'>

저렇게 되 있다면,

Temporary Internet Files 디렉토리만 뒤져봐도 키값이 노출되겠지요...

그러니 페이지에 남기지 않고 로그아웃시 메모리에서 사라지게
lifetime 0 짜리 1회용 쿠키나 세션에 담아쓰면 좀 낳을 수 있겠지요...

페이지에서 쿠키값을 검색해서 '키값'을 뽑아내고 메모리 상에서만 연산을 하면 되겠지요..

저렇게나마 해 두면 네트웍 감시를 당해도

id = 'abcd'
pw = '1246'
이라는 해쉬된 값이 나오기 때문에 원래의 비밀번호 '1234' 는
'키값'을 알기 전에는 무용지물이 되겠지요....

그리고  login 페이지로 '1246'을 날려도 소용없게 해야죠...
주고받는 그 때만 생성되는 유니크한 값을 '키값'으로 사용해서
어떨 때는 '1246' 이 '1234' 로 해석되지만,
어떨 때는 '3266' 이 '1234' 로 해석이 되게 하는 등.....
지속적으로 키값이 바뀌게 해야 하는 것이죠.....


보통 setCookie("hashKey", rand( 1000, 9000), 0 ) 식으로 라이프타임을 0 으로 주면
페이지 접속창이 닫힐 때 까지만 한번 정해진 '키값'이 유효한 상태가 되고,
이후 새로운 창이 열리거나 기존 창을 닫으면 사라지게 되지요..

쿠키도 마찬가지로 라이프타임을 0 으로 주면 되구요,.,



------------- 이정도 가지만 해도 ---------------------------------------------------

# DB 가 노출되었을 때 사용자들을 보호할 수 있습니다.

# 네트웍이 감시를 당해도 원래의 아이디를 모를 뿐 아니라
    해쉬된 스트링으로 로그인을 시도해도 키값이 수시로 변하기에 안전할 수 있습니다.



물론 헤커가 세션과, 쿠키까지 파싱하고, 서버디렉토리도 들락날락거린다면 속수무책이 되게지만,
최소한 실력없는 헤커로부터는 안전을 보장받을 수 있게 됩니다.




아참,
mysql 의 password() 함수를 실험 안 해 보았군요....



id = 'abcd'
pw = '4ed0bdda4ee8f6a5'

<?
for( $i = 0; $i < 99999; $i ++ ) {

       $temp = mysql_fetch_array( mysql_query ( "select password('$i')" ) );

       if( '4ed0bdda4ee8f6a5' == $temp[0] ) {
              echo " find ok : $i ";
              break;
       }
}
// 출력 -> find ok : 50267
?>

 

출처 : http://www.phpschool.com

'DB > MySQL' 카테고리의 다른 글

mysql - 파일에서 테이블로 insert  (0) 2012.07.13
mysql - 행 번호 매기기  (1) 2012.06.23
mysql에서 limit 사용  (0) 2012.05.02
MySQL 로그 파일 관리 2 - 로그 파일 남기기  (0) 2012.05.02
mysql 외래키 옵션  (0) 2012.04.06
Posted by linuxism
,

/etc/shadow

System/Linux 2012. 5. 25. 06:32



In computingUnix-like operating systems use the shadow password database mechanism to increase the security level of passwords by restricting all but highly privileged users' access to encrypted password data. Typically, that data is kept in files owned by and accessible only by the super user (i.e., on Unix-like systems, the root user, and on many others, the administrator account).

Contents

  [hide

[edit]Design

Systems administrators can reduce the likelihood of brute force attacks by making the list of hashed passwords unreadable by unprivileged users. The obvious way to do this is to make the passwd database itself readable only by the root user. However, this would restrict access to other data in the file such as username-to-userid mappings, which would break many existing utilities and provisions. One solution is a "shadow" password file to hold the password hashes separate from the other data in the world-readable passwd file. For local files, this is usually /etc/shadow on Linux and Unix systems, or /etc/master.passwd on BSD systems; each is readable only by root. (Root access to the data is considered acceptable since on systems with the traditional "all-powerful root" security model, the root user would be able to obtain the information in other ways in any case). Virtually all recent Unix-likeoperating systems use shadowed passwords.

The shadow password file does not entirely solve the problem of attacker access to hashed passwords, as some network authentication schemes operate by transmitting the encrypted password over the network (sometimes in cleartext[citation needed]), making it vulnerable to interception. Copies of system data, such as system backups written to tape or optical media, can also become a means for illicitly obtaining hashed passwords. In addition, the functions used by legitimate password-checking programs need to be written in such a way that malicious programs cannot make large numbers of authentication checks at high rates of speed.

[edit]Usage

On a system without shadowed passwords (typically older Unix systems dating from before 1990 or so), the passwd file holds the following user information for each user account:

The passwd file is readable by all users so that name service switch can work (e.g., to ensure that user names are shown when the user lists the contents of a folder), but only the root user can write to it. This means that an attacker with unprivileged access to the system can obtain the hashed form of every user's password. Those values can be used to mount abrute force attack offline, testing possible passwords against the hashed passwords relatively quickly without alerting system security arrangements designed to detect an abnormal number of failed login attempts. Users often select passwords vulnerable to such password cracking techniques.[1]

With a shadowed password scheme in use, the /etc/passwd file typically shows a character such as '*', or 'x' in the password field for each user instead of the hashed password, and /etc/shadow usually contains the following user information:

  • User login name
  • salt and hashed password OR a status exception value e.g.:
    • "$id$salt$encrypted", where "$id" is the hashing algorithm used (On GNU/Linux, "$1$" stands for MD5, "$2$" is Blowfish, "$5$" is SHA-256 and "$6$" is SHA-512crypt(3) manpage, other Unix may have different values, like NetBSD).
    • "NP" or "!" or null - No password, the account has no password.
    • "LK" or "*" - the account is Locked, user will be unable to log-in
    • "!!" - the password has expired
  • Days since epoch of last password change
  • Days until change allowed
  • Days before change required
  • Days warning for expiration
  • Days before account inactive
  • Days since Epoch when account expires
  • Reserved

The format of the shadow file is simple, and basically identical to that of the password file, to wit, one line per user, ordered fields on each line, and fields separated by colons. Many systems require the order of user lines in the shadow file be identical to the order of the corresponding users in the password file.

To modify the contents of the shadow file on most systems, users generally invoke the passwd program, which in turn largely depends on PAM. For example, the type of hash used is dictated by the configuration of the pam_unix.so module. By default, the MD5 hash has been used, while current modules are also capable of stronger hashes such as blowfish,SHA256 and SHA512.

[edit]History

Password shadowing first appeared in UNIX systems with the development of System V Release 3.2 in 1988 and BSD4.3 Reno in 1990. But, vendors who had performed ports from earlier UNIX releases did not always include the new password shadowing features in their releases, leaving users of those systems exposed to password file attacks.

System administrators may also arrange for the storage of passwords in distributed databases such as NIS and LDAP, rather than in files on each connected system. In the case of NIS, the shadow password mechanism is often still used on the NIS servers; in other distributed mechanisms the problem of access to the various user authentication components is handled by the security mechanisms of the underlying data repository.

In 1987 the author of the original Shadow Password Suite, Julie Haugh, experienced a computer break-in and wrote the initial release of the Shadow Suite containing the loginpasswdand su commands. The original release, written for the SCO Xenix operating system, quickly got ported to other platforms. The Shadow Suite was ported to Linux in 1992 one year after the original announcement of the Linux project, and was included in many early distributions, and continues to be included in many current Linux distributions.

[edit]See also



'System > Linux' 카테고리의 다른 글

linux - 프로세스 디버깅  (0) 2012.07.09
Linux - iptables를 이용한 포트포워딩  (1) 2012.05.26
BIND 설치  (0) 2012.05.16
chroot 명령어  (0) 2012.05.16
우분투, 서버 시장서 레드햇에 도전장  (0) 2012.04.08
Posted by linuxism
,